<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 4.2.1">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/%E6%AD%A6%E6%B1%8932x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/%E6%AD%A6%E6%B1%8916x16.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib1/font-awesome/css/all.min.css">
  <link rel="stylesheet" href="/lib1/pace/pace-theme-center-simple.min.css">
  <script src="/lib1/pace/pace.min.js"></script>

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"example.com","root":"/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":true,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta property="og:type" content="website">
<meta property="og:title" content="Technological Blog">
<meta property="og:url" content="http://example.com/page/6/index.html">
<meta property="og:site_name" content="Technological Blog">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="Li Yudong">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="http://example.com/page/6/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : true,
    isPost : false,
    lang   : 'zh-CN'
  };
</script>

  <title>Technological Blog</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">Technological Blog</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">IT小白的成长之旅</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="tags fa-fw"></i>标签<span class="badge">18</span></a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="th fa-fw"></i>分类<span class="badge">14</span></a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="archive fa-fw"></i>归档<span class="badge">95</span></a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

  <a href="https://github.com/tiarmor1" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content index posts-expand">
            
      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/07/10/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93/%E4%BB%8ECORS%E5%BC%80%E5%A7%8B%E7%AE%80%E8%BF%B0HTTP%E5%A4%B4/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/07/10/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93/%E4%BB%8ECORS%E5%BC%80%E5%A7%8B%E7%AE%80%E8%BF%B0HTTP%E5%A4%B4/" class="post-title-link" itemprop="url">从CORS开始的HTTP首部概述</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-07-10 21:28:16" itemprop="dateCreated datePublished" datetime="2021-07-10T21:28:16+08:00">2021-07-10</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:24:31" itemprop="dateModified" datetime="2021-11-03T16:24:31+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E5%89%8D%E7%AB%AF%E9%A1%B5%E9%9D%A2/" itemprop="url" rel="index"><span itemprop="name">前端页面</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="HTTP首部概述"><a href="#HTTP首部概述" class="headerlink" title="HTTP首部概述"></a>HTTP首部概述</h1><h2 id="CORS"><a href="#CORS" class="headerlink" title="CORS"></a>CORS</h2><h3 id="同源策略"><a href="#同源策略" class="headerlink" title="同源策略"></a>同源策略</h3><p>同源策略（Same origin policy）是一种约定，它是浏览器最核心也最基本的安全功能，如果缺少了同源策略，则浏览器的正常功能可能都会受到影响。可以说 Web 是构建在同源策略基础之上的，浏览器只是针对同源策略的一种实现。它们应该只被允许访问来自同一站点的资源，而不是那些来自其它站点可能怀有恶意的资源。</p>
<p>所谓同源是指：<strong>域名、协议、端口相同</strong>。同源策略又分为以下两种：</p>
<p>1、DOM 同源策略：禁止对不同源页面 DOM 进行操作。这里主要场景是 iframe 跨域的情况，不同域名的 iframe 是限制互相访问的。2、XMLHttpRequest 同源策略：禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。3、禁止读取Cookie、LocalStorage、IndexDB。</p>
<p>如果没有 DOM 同源策略，也就是说不同域的 iframe 之间可以相互访问，那么黑客可以这样进行攻击：1、做一个假网站，里面用 iframe 嵌套一个银行网站 <a href="http://mybank.com.2、把/" target="_blank" rel="noopener">http://mybank.com。2、把</a> iframe 宽高啥的调整到页面全部，这样用户进来除了域名，别的部分和银行的网站没有任何差别。3、这时如果用户输入账号密码，我们的主网站可以跨域访问到 <a href="http://mybank.com/" target="_blank" rel="noopener">http://mybank.com</a> 的 dom 节点，就可以拿到用户的账户密码了。</p>
<p>如果 XMLHttpRequest 同源策略，那么黑客可以进行 CSRF（跨站请求伪造） 攻击：1、用户登录了自己的银行页面 <a href="http://mybank.com,http//mybank.com" target="_blank" rel="noopener">http://mybank.com，http://mybank.com</a> 向用户的 cookie 中添加用户标识。2、用户浏览了恶意页面 <a href="http://evil.com,执行了页面中的恶意/" target="_blank" rel="noopener">http://evil.com，执行了页面中的恶意</a> AJAX 请求代码。3、<a href="http://evil.com/" target="_blank" rel="noopener">http://evil.com</a> 向 <a href="http://mybank.com/" target="_blank" rel="noopener">http://mybank.com</a> 发起 AJAX HTTP 请求，请求会默认把 <a href="http://mybank.com/" target="_blank" rel="noopener">http://mybank.com</a> 对应 cookie 也同时发送过去。4、银行页面从发送的 cookie 中提取用户标识，验证用户无误，response 中返回请求数据。此时数据就泄露了。</p>
<h3 id="JSONP"><a href="#JSONP" class="headerlink" title="JSONP"></a>JSONP</h3><p>由于 script 标签不受浏览器同源策略的影响，允许跨域引用资源。因此可以通过动态创建 script 标签，然后利用 src 属性进行跨域，这也就是 JSONP 跨域的基本原理。<code>JSONP（JSON with Padding）</code> ，它是一个 <strong>非官方</strong> 的协议，它允许在服务器端集成 <code>Script tags</code> 返回至客户端，通过 <code>javascript callback</code> 的形式实现跨域访问，但仅限于Get请求，这就是简单的JSONP实现形式，简单实例如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 定义一个 回调函数 handleResponse 用来接收返回的数据</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">handleResponse</span>(<span class="params">data</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(data);</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">// 2. 动态创建一个 script 标签，并且告诉后端回调函数名叫 handleResponse</span></span><br><span class="line"><span class="keyword">var</span> body = <span class="built_in">document</span>.getElementsByTagName(<span class="string">'body'</span>)[<span class="number">0</span>];</span><br><span class="line"><span class="keyword">var</span> script = <span class="built_in">document</span>.gerElement(<span class="string">'script'</span>);</span><br><span class="line">script.src = <span class="string">'http://www.laixiangran.cn/json?callback=handleResponse'</span>;</span><br><span class="line">body.appendChild(script);</span><br><span class="line"><span class="comment">// 3. a通过 script.src 请求 `http://www.laixiangrn.cn/json?callback=handleResponse`，</span></span><br><span class="line"><span class="comment">// 4. 后端能够识别这样的 URL 格式并处理该请求，然后返回 handleResponse(&#123;"name": "laixiangran"&#125;) 给浏览器；返回的是一个回调函数</span></span><br><span class="line"><span class="comment">// 5. 浏览器在接收到 handleResponse(&#123;"name": "laixiangran"&#125;) 之后立即执行 ，也就是执行 handleResponse 方法，</span></span><br><span class="line"><span class="comment">//获得后端返回的数据，这样就完成一次跨域请求了。</span></span><br><span class="line"><span class="comment">//JSONP是一段参数是json格式(大多数情况)的JS代码</span></span><br></pre></td></tr></table></figure>

<p>创建一个回调函数，然后在远程服务上调用这个函数并且将JSON 数据形式作为参数传递，完成回调，就是 <code>JSONP</code> 的简单实现模式，或者说是 <code>JSONP</code> 的原型。上面这种实现很简单，通常情况下，我们希望这个 <code>script</code> 标签能够动态的调用，而不是像上面因为固定在 <code>HTML</code> 里面加载时直接执行了，很不灵活，我们可以通过 <code>javascript</code> 动态的创建 <code>script</code> 标签，这样我们就可以灵活调用远程服务了。</p>
<h3 id="CORS-1"><a href="#CORS-1" class="headerlink" title="CORS"></a>CORS</h3><p>CORS<code>是一个W3C标准，全称是</code>跨域资源共享（Cross-origin resource sharing）。它允许浏览器向跨源服务器，发出 <code>XMLHttpRequest</code> 请求，从而克服了 <code>AJAX</code> 只能同源使用的限制。<code>CORS</code> 需要浏览器和服务器同时支持，目前基本所有浏览器都支持该功能，IE浏览器不低于 IE10 即可。因此，实现 <code>CORS</code> 通信的关键是服务器，只要服务器设置了允许的 <code>CORS</code> 接口，就可以进行跨源通信，要了解怎么实现 <code>CORS</code> 跨域通信，我们还要先了解浏览器对每个请求都做了什么</p>
<h4 id="简单请求"><a href="#简单请求" class="headerlink" title="简单请求"></a>简单请求</h4><p>1、请求方法是HEAD、GET、POST其中之一；</p>
<p>2、且请求头信息不超过一下：Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type。</p>
<p>对于简单请求，浏览器会直接发送CORS请求，就是在该请求的头信息中，自动添加一个Origin字段，来说明本次请求的来源（协议+域名+端口），而后服务i器会根据该值判断是否同意请求。</p>
<h4 id="非简单请求"><a href="#非简单请求" class="headerlink" title="非简单请求"></a>非简单请求</h4><p>对于非简单请求，浏览器会在正式通信之前做一次查询请求，叫做预检请求，也叫OPTIONS请求，请求方式为OPTIONS，用于询问。浏览器会先询问服务器，当前网页所在的域名是否在服务器的许可名单中，以及可以使用哪些HTTP动词和头信息字段。</p>
<p>预检请求头信息添加字段：Origin(表明来源)、Access-Control-Request-Method(浏览器CORS请求用到的HTTP请求方法)、Access-Control-Request-Headers(浏览器CORS请求额外发送的头信息字段)。</p>
<h4 id="Cookie"><a href="#Cookie" class="headerlink" title="Cookie"></a>Cookie</h4><p>CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发送到服务端，则需服务端、客户端均开启请求。服务端设置Access-Control-Allow-Credentials；客户端打开<code>withCredentials</code> 属性。同时，<code>Cookie</code> 依然遵循同源政策，只有用服务器域名设置的 <code>Cookie</code> 才会上传，其他域名的 <code>Cookie</code> 并不会上传，且（跨源）原网页代码中的 <code>document.cookie</code> 也无法读取服务器域名下的 <code>Cookie</code>。</p>
<h4 id="服务端配置"><a href="#服务端配置" class="headerlink" title="服务端配置"></a>服务端配置</h4><p><code>CORS</code> 跨域请求中，最关键的就是 <code>Access-Control-Allow-Origin</code> 字段，是必需项，它表示服务端允许跨域访问的地址来源，你可以写入需要跨域的域名，也可以设为星号，表示同意任意跨源请求。</p>
<p>Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT…。配置允许跨域请求的方法。</p>
<p>Access-Control-Allow-Headers： x-requested-with,content-type…配置允许的请求头字段。</p>
<p>Access-Control-Allow-Credentials: true 配置是否允许发送Cookie。</p>
<p>Access-Control-Max-Age：配置本次预检请求的有效期</p>
<h2 id="状态码"><a href="#状态码" class="headerlink" title="状态码"></a>状态码</h2><p>在梳理HTTP请求头之前，先简单梳理一下HTTP的状态码。</p>
<h3 id="信息性状态码100-199"><a href="#信息性状态码100-199" class="headerlink" title="信息性状态码100~199"></a>信息性状态码100~199</h3><p>100：（Continue）当客户端要向服务器发送一个大实体前，先向服务器发送一个携带了值为100Continue的Expect请求首部；然后等待服务器对该请求响应，其实类似于CORS的预检请求。</p>
<p>101：(Switching Protocols)正在切换协议。</p>
<h3 id="成功状态码200-299"><a href="#成功状态码200-299" class="headerlink" title="成功状态码200~299"></a>成功状态码200~299</h3><p>200：（OK）请求被正常处理。</p>
<p>202：（Accepted）请求已被接受，但服务器并未执行其他动作。</p>
<p>204：（No Content）响应报文中包含HTTP首部与状态行，但无实体的主体。主要用于浏览器不转为显示新文档情况下，对其进行更新。</p>
<h3 id="重定向状态码300-399"><a href="#重定向状态码300-399" class="headerlink" title="重定向状态码300~399"></a>重定向状态码300~399</h3><p>重定向状态码要么提供客户端使用替代位置来访问资源；要么提供一个替代的响应而非资源内容。</p>
<p>301：（Moved Permanently）永久重定向，请求的资源已经被分配新的URL，旧资源已经永久移除。响应的Location首部中包含资源现存的URL。</p>
<p>302：（Found）临时重定向，客户端用Location首部地URL临时定位资源，之后还是使用老URL。</p>
<p>304：（Not Modified）1、客户端发送附带条件请求，服务器找到资源但不符合请求条件；2、客户端发起条件GET请求，而资源最近未被修改，该状态码用于协商缓存。</p>
<h3 id="客户端错误状态码400-499"><a href="#客户端错误状态码400-499" class="headerlink" title="客户端错误状态码400~499"></a>客户端错误状态码400~499</h3><p>400：（Bad Request）请求报文有语法错误；需要修改请求内容后再次发送请求；</p>
<p>403：（Forbidden）用于说明请求被服务器拒绝了。</p>
<p>404：（Not Found）说明服务器上无法找到请求地资源。</p>
<h3 id="服务错误状态码500-599"><a href="#服务错误状态码500-599" class="headerlink" title="服务错误状态码500~599"></a>服务错误状态码500~599</h3><p>500：（）服务器执行请求时错误；</p>
<p>503：（）服务器现在无法为请求提供服务；</p>
<h2 id="首部"><a href="#首部" class="headerlink" title="首部"></a>首部</h2><h3 id="通用首部"><a href="#通用首部" class="headerlink" title="通用首部"></a>通用首部</h3><p>通用首部提供了与报文相关地基本信息；其中通用信息首部包括：Connection；Date；MIME-Version；Trailer；Transfer-Encoding；Updata；Via。</p>
<p>通用缓存首部：Cathe-Control（用于强缓存，与Expires配合，设置某个绝对时间点限定读取缓存时间）。Cathe-Control保存相对时间，优先级高于Expires。</p>
<p>当服务器返回响应时，在 Response Headers 中将过期时间写入 expires 字段。接下来如果我们试图再次向服务器请求资源，浏览器就会先对比本地时间和 expires 的时间戳，如果本地时间小于 expires 设定的过期时间，那么就直接去缓存中取这个资源。expires写的是一个绝对的时间戳，例如：xxx年x月x日。而在 Cache-Control 中，我们通过 <code>max-age</code>字段 来控制资源的有效期。max-age 不是一个时间戳，而是一个时间长度。max-age 是一个相对时间，这就意味着它有能力规避掉 expires 可能会带来的时差问题。同样，因此cache-control的优先级比expires更高。Cache-Control 中还有更高优先级的s-maxage：用于表示 cache 服务器上（比如 cache CDN）的缓存的有效时间的，并只对 public 缓存有效。（public 与 private 是针对资源是否能够被代理服务缓存而存在的一组对立概念。）</p>
<h3 id="请求首部"><a href="#请求首部" class="headerlink" title="请求首部"></a>请求首部</h3><p>请求首部只在请求报文中有意义，说明发送请求者相关信息。其中信息首部包括：Client-IP、From、Host、Referer等。</p>
<p>Accept首部：为客户端提供将其喜好和能力告知服务器的方式。-Charset，-Encoding，-Language。</p>
<p>条件请求首部：客户端对请求加上的限制，要求服务器在响应前，确保某个条件为真。</p>
<p>安全请求首部：HTTP本身的简单安全措施，包括Authorization、Cookie，用于密码登录的token验证</p>
<p>代理请求首部：用于网络代理模式。</p>
<h3 id="响应首部"><a href="#响应首部" class="headerlink" title="响应首部"></a>响应首部</h3><p>响应报文有其响应首部集，有助于客户端处理响应，其信息首部包括：Age、Public、Server等。</p>
<p>协商首部：当资源有多种表示方法时，提供协商能力；</p>
<p>安全响应首部：Set-Cookie，在客户端设置一个令牌，以便服务端对客户端标识。</p>
<h3 id="实体首部"><a href="#实体首部" class="headerlink" title="实体首部"></a>实体首部</h3><p>信息性首部包括：Allow、Location。Location用于告诉客户端实体资源实际位于何处。</p>
<p>内容首部：Content.</p>
<p>实体缓存首部：Expires（保存强缓存资源的绝对过期时间）、ETag、Last-Modified。</p>
<p>ast-Modified 到 Etag**。Last-Modified 是一个时间戳，如果我们启用了协商缓存，它会在首次请求时随着 Response Headers 返回。随后我们每次请求时，会带上一个叫 If-Modified-Since 的时间戳字段，它的值正是上一次 response 返回给它的 last-modified 值。服务器接收到这个时间戳后，会比对该时间戳和资源在服务器上的最后修改时间是否一致，从而判断资源是否发生了变化。如果发生了变化，就会返回一个完整的响应内容，并在 Response Headers 中添加新的 Last-Modified 值；否则，返回如上图的 304 响应，Response Headers 不会再添加 Last-Modified 字段。</p>
<p>但是可能会有一个bug：我们编辑文件，但没有修改，服务器可能会以为我们修改了；修改文件的时间过快，服务器可能会感知不到。即：服务器并没有正确感知文件的变化。这样就引出了Etag，Etag 是由服务器为每个资源生成的唯一的<strong>标识字符串</strong>，这个标识字符串是基于文件内容编码的，只要文件内容不同，它们对应的 Etag 就是不同的。Etag 的生成过程需要服务器额外付出开销，会影响服务端的性能，这是它的弊端。同样，优先级方面，Etag高于Last-Modefied。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/07/10/vue%E6%BA%90%E7%A0%81/vue%E5%88%86%E6%9E%90/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/07/10/vue%E6%BA%90%E7%A0%81/vue%E5%88%86%E6%9E%90/" class="post-title-link" itemprop="url">vue分析</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-07-10 12:18:39" itemprop="dateCreated datePublished" datetime="2021-07-10T12:18:39+08:00">2021-07-10</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:21:41" itemprop="dateModified" datetime="2021-11-03T16:21:41+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/VUE/" itemprop="url" rel="index"><span itemprop="name">VUE</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="源码思维"><a href="#源码思维" class="headerlink" title="源码思维"></a>源码思维</h1><h2 id="健壮性"><a href="#健壮性" class="headerlink" title="健壮性"></a>健壮性</h2><p>代码在发生预期之外的错误时，其应对错误的能力：即就算出错也能够轻松定位到错误，且减少错误的影响范围。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//1、不要轻易去相信传入参数，需要判断参数类型</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">add</span>(<span class="params">a, b</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> a == <span class="string">'number'</span> &amp;&amp; <span class="keyword">typeof</span> b == <span class="string">'number'</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> a + b;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'a'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//2、易错代码用try catch包裹起来</span></span><br><span class="line">a[<span class="number">0</span>] &amp;&amp; a[<span class="number">0</span>].data[<span class="number">0</span>];<span class="comment">//取属性之前，先判断其是否存在</span></span><br></pre></td></tr></table></figure>

<h2 id="defineProperty"><a href="#defineProperty" class="headerlink" title="defineProperty"></a>defineProperty</h2><p>主要用于vue的数据绑定，</p>
<p>使用defineProperty中的get来进行设置属性时，这个属性不能被改值，因为只写了get，只能够取到值而不能改值；要能够改值则需要写上set。其实这是一种变量权限的问题，如果该变量是某个属性的话，则可以使用defineProperty来控制其各类权限。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">this</span>.$router = <span class="keyword">this</span>._root._router;</span><br><span class="line"><span class="built_in">Object</span>.defineProperty(<span class="keyword">this</span>, <span class="string">'$router'</span>, &#123;</span><br><span class="line">    <span class="keyword">get</span>() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>._root._router;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>模块支持检测：先判断当前的环境符合的哪种模块规范；cmd、amd、umd，或者CommonJS规范、import规范。</p>
<h2 id="架构模式"><a href="#架构模式" class="headerlink" title="架构模式"></a>架构模式</h2><h3 id="工厂模式"><a href="#工厂模式" class="headerlink" title="工厂模式"></a>工厂模式</h3><p>通俗而讲：我去构建一个工厂方法，让使用者调用这个工厂方法去拿到他要的对象，而不是自己去构建，即不需要去new。</p>
<p>典型例子：jquery。使用jquery时是DOM时代，频繁获取并操作DOM。工厂模式直接用$()就可以拿到对象。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//jQuery</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">window</span>) </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">jquery</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> jquery.fn.init();</span><br><span class="line">    &#125;</span><br><span class="line">    jquery.fn.init.prototype = jquery.fn;</span><br><span class="line">    jquery.fn = jquery.prototype = &#123;</span><br><span class="line">        init:<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">            </span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    jquery.extend(&#123;</span><br><span class="line">        <span class="comment">//回调。给一个对象，将其添加到jquery上</span></span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="comment">//这样去包装是为了jquery中的方法可以有多种调用方式，</span></span><br><span class="line">    <span class="comment">//但对象为引用类型，var b = &#123;&#125;, a = b, c = b,修改b时a、c均会修改，实现改一个均影响到。</span></span><br><span class="line">    <span class="built_in">window</span>.$ = <span class="built_in">window</span>.jquery = jquery;</span><br><span class="line">&#125;)(<span class="built_in">window</span>);<span class="comment">//定义一个匿名自执行函数，不污染外部全局变量，</span></span><br><span class="line"><span class="comment">//同时后面直接传参(window),减少作用链长度</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//vue</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span> (<span class="params">global, factory</span>) </span>&#123;</span><br><span class="line">    </span><br><span class="line">&#125;(<span class="keyword">this</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    </span><br><span class="line">&#125;));<span class="comment">//this在浏览器环境为window，在node环境为global，不会去写死</span></span><br></pre></td></tr></table></figure>

<h3 id="建造者模式"><a href="#建造者模式" class="headerlink" title="建造者模式"></a>建造者模式</h3><p>在构建一个庞大的框架时，先把整个框架分开成几个模块，=》预制=》然后将各个模块融合在一起。</p>
<p>典型例子：vue-2，构建一个庞大的框架。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Vue</span>(<span class="params">options</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!(<span class="keyword">this</span> <span class="keyword">instanceof</span> Vue)) &#123;</span><br><span class="line">        warn(<span class="string">'Vue is a constructor'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">this</span>._init(options);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">initMixin(Vue);</span><br><span class="line">stateMixin(Vue);</span><br><span class="line">eventsMixin(Vue);</span><br><span class="line">lifecycleMixin(Vue);</span><br><span class="line">renderMixin(Vue);</span><br><span class="line"><span class="comment">/*为什么非要分开开发，而不是全部放到原型链上即可。</span></span><br><span class="line"><span class="comment">1、由于是团队开发，全放在prototype，可能会收到相互之间的影响</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<h3 id="函数式"><a href="#函数式" class="headerlink" title="函数式"></a>函数式</h3><p>整体的功能就是一大堆的函数，通过函数之间的相互调用来实现整体的功能。对于javaScript，函数才是一等公民：1、tree-shaking：基于文档流的原理，判断某个函数是否被调用，因此能够webpack时自动删除未被调用的函数。2、组合大于继承。因此函数式编程适用于工具库。</p>
<p>典型例子：Vue3，</p>
<h2 id="设计模式"><a href="#设计模式" class="headerlink" title="设计模式"></a>设计模式</h2><p>vue-router、vuex这样的只能有一个，即典型的一个类只有一个实例化对象，即单例模式。实现很容易，就是先判断是否已经被实例化过，如果已经实例化过，就不再实例化；需要通过一个变量判断是否已经实例化过。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> _vue;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">install</span>(<span class="params">vue</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (install.installed &amp;&amp; _vue == vue) <span class="keyword">return</span>;</span><br><span class="line">    <span class="comment">//执行安装</span></span><br><span class="line">    install.installed = <span class="literal">true</span>;</span><br><span class="line">    _vue = vue;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>代码简洁技巧：jquery中的extend({})方法，传入两个对象时，会将两个对象合并再传入。享元模式：减少重复的代码块的数量，将不同点提取出来作为参数。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> target = <span class="keyword">this</span>;</span><br><span class="line">    <span class="keyword">let</span> source = <span class="built_in">arguments</span>[<span class="number">0</span>];</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">arguments</span>.length == <span class="number">2</span>) &#123;</span><br><span class="line">        target = <span class="keyword">this</span>;</span><br><span class="line">        source = <span class="built_in">arguments</span>[<span class="number">1</span>];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> item <span class="keyword">in</span> source) &#123;</span><br><span class="line">        target[item] = source[item];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>优雅利用原生方法：vue使用defineProperty来实现双向绑定，但只能应用于对象的属性；那么数组怎么来触发更新？将数组原生的push、pop、shift方法加上了能够触发双向绑定的功能。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> arr = [<span class="string">"push"</span>, <span class="string">"pop"</span>];</span><br><span class="line"><span class="keyword">var</span> arrayProto = <span class="built_in">Array</span>.prototype;</span><br><span class="line"><span class="keyword">let</span> arrayMethods = <span class="built_in">Object</span>.create(arraryProto);</span><br><span class="line"><span class="comment">//执行以下拷贝，来避免污染原生的原型链</span></span><br><span class="line">arr.forEach(<span class="function">(<span class="params">methos</span>) =&gt;</span> &#123;</span><br><span class="line">    arrMethods[method] = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">let</span> original = arrayProto[method];</span><br><span class="line">        <span class="keyword">let</span> result = original.apply(<span class="keyword">this</span>, args);</span><br><span class="line">        <span class="comment">//先找到需要修改的方法，并先将原生方法的功能附加上去；</span></span><br><span class="line">        dep.noyify();<span class="comment">//触发数组更新</span></span><br><span class="line">        <span class="keyword">return</span> result;<span class="comment">//保持返回值与原方法一致</span></span><br><span class="line">        <span class="comment">//最后只替换数组中的原型链即可</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h1 id="应用进阶"><a href="#应用进阶" class="headerlink" title="应用进阶"></a>应用进阶</h1><h2 id="缓存架构-API"><a href="#缓存架构-API" class="headerlink" title="缓存架构-API"></a>缓存架构-API</h2><p>优化方案：快、小、省。</p>
<p>缓存：是所有优化方案里最常用、最有效、可以节省http请求，可以从内存中最快读取数据。</p>
<p>1、选择更快的API、算法，减少时间复杂度；</p>
<p>2、看到网站主要消耗的时间，更多地并不在于代码的执行速度，而在于资源加载的速度，因此文件体积小更重要。一、用webpack压缩treeshaking；二、减少代码重复；</p>
<p>vue3方面的改进：1、快：更快的API，用proxy来代替defineProperty；代理proxy不会去改变原对象；diff算法方面改进：对比新老的DOM，先分析动态DOM，只比对动态的DOM；2、小：函数式API：拥抱tree-shaking，可以很方便地将没有用到的方法取出来，并不附加在工程文件中。</p>
<p>3、省：节省http请求</p>
<p>读取数据的快慢：最慢：网络请求；其次：从硬盘中拿数据；最快：从内存中拿数据。因此请求缓存思路就是：有缓存的话，不去发送请求而是直接从缓存中拿去数据。</p>
<p>缓存架构的技巧：</p>
<p>1、缓存架构需要使用单例模式，即所有的缓存都放在一个里面。</p>
<p>2、权限问题，存缓的区域不能直接拿出去给别人操作。因此，需要隐藏起来，使用匿名自执行函数来进行；但同样，需要外界能够进行读取缓存操作，因此需要返回一个对象，在该对象内有一系列操作缓存的方法。这其实是函数闭包的应用。</p>
<p>3、缓存都会存在副作用：</p>
<p>一、更新问题：使用localstorage、session、cookies缓存时，缓存至本地，需要考虑更新问题。(1)、与后端进行websocket连接，但过于麻烦，没必要；(2)、通过定期轮询，定时器，每隔一段时间向后端发起ajax请求；经常变动的API不要用这种缓存；</p>
<p>使用内存缓存时，是不用考虑更新问题；但内存缓存同样有自己的问题，需要考虑占用内存问题，每往里面加东西，都会占用内存。javascript的内存限制较严重，因此需要做一个限制。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!<span class="built_in">window</span>.mycache) &#123;</span><br><span class="line">    <span class="built_in">window</span>.mycache = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">var</span> cache = &#123;&#125;;</span><br><span class="line">        <span class="keyword">var</span> cacheArr = [];</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="keyword">get</span>: function (api) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">                    <span class="comment">//promise实现异步以及链式调用</span></span><br><span class="line">                 <span class="keyword">if</span> (cache[api])&#123;</span><br><span class="line">                    resolve(cache[api])</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">this</span>.set(api).then(<span class="function"><span class="params">()</span>=&gt;</span> &#123;</span><br><span class="line">                        <span class="keyword">if</span> (cacheArr.length &gt; <span class="number">10</span>) &#123;</span><br><span class="line">                            <span class="keyword">let</span> _api = cacheArr.shift();</span><br><span class="line">                            <span class="keyword">this</span>.remove(_api);</span><br><span class="line">                        &#125;</span><br><span class="line">                        cache[api] = res;</span><br><span class="line">                        cacheArr.push(api);</span><br><span class="line">                        <span class="comment">//需要确保内存限制，不要占用太多内存</span></span><br><span class="line">                        resolve(res);</span><br><span class="line">                    &#125;)</span><br><span class="line">                &#125;</span><br><span class="line">                &#125;)</span><br><span class="line">            &#125;,</span><br><span class="line">            <span class="keyword">set</span>: function () &#123;</span><br><span class="line">                <span class="keyword">return</span> axios.get(api);</span><br><span class="line">            &#125;,</span><br><span class="line">            remove: <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">                </span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;)();</span><br><span class="line">    <span class="comment">//放在匿名自执行函数里面</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="vue插件"><a href="#vue插件" class="headerlink" title="vue插件"></a>vue插件</h2><p>关键的两个API：Vue.use；Vue.mixin。</p>
<p>Vue.use();原理：将一个方法执行一次，如果该方法中有install属性，就执行其install属性，来代替执行该方法；</p>
<p>Vue.mixin()；原理：相当于一个全局混合；(局部混合相当于在组件的export后写mixin)，可以混合方法methods、数据data、混合生命周期(生命周期的混入才是核心功能)。即，混入的生命周期操作，所有的组件在对应的生命周期均执行该操作函数。</p>
<p>打包项目=》打包体积过大，可以使用异步加载来减少体积 =》 vuex内容太大；=》vuex异步加载，根据组件来异步加载vuex，放在beforeCreated阶段，在DOM还没有加载好之前，将其之间的依赖关系读取好。 </p>
<p>插件的用途：1、提供逻辑复用；2、注入自定义操作；</p>
<h2 id="常用API"><a href="#常用API" class="headerlink" title="常用API"></a>常用API</h2><p>Vue.util.defineReative：其实就是Vue自身去做响应式的方法，；一般来说只有data的数据能触发响应。这里，我们可以调用这个API，使外部数据也能触发响应。</p>
<p>用法：1、将localstorage中的数据触发响应，不需要将其取出来；2、监听window的事件，常用的是resize，实现尺寸的自适应；3、监听浏览器版本、内存；</p>
<p>本质是让外部数据也能触发响应。</p>
<p>Vue.extend：传入一个对象时，其会返回一个构造函数，直接new便可以创建实例。用于import，在使用import时，导入的是vue的选项。</p>
<p>vue单元测试=》测某个组件的方法和节点渲染结果</p>
<p>render渲染：提供函数，用JS来表达你的整个template，当页面的逻辑较为复杂时。</p>
<h1 id="API层"><a href="#API层" class="headerlink" title="API层"></a>API层</h1><h2 id="Axios源码分析"><a href="#Axios源码分析" class="headerlink" title="Axios源码分析"></a>Axios源码分析</h2><h3 id="Axios如何实现请求拦截、响应拦截"><a href="#Axios如何实现请求拦截、响应拦截" class="headerlink" title="Axios如何实现请求拦截、响应拦截"></a>Axios如何实现请求拦截、响应拦截</h3><p>1、初始化Axios，其实是调用request方法；</p>
<p>请求拦截器设置的处理——发送请求——响应拦截器设置的处理：这些一大堆方法由数组来存储；</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">axios.interceptors.request.use(<span class="function"><span class="params">()</span>=&gt;</span> &#123;</span><br><span class="line">    </span><br><span class="line">&#125;)</span><br><span class="line">axios.interceptors.response.use(<span class="function"><span class="params">()</span>=&gt;</span> &#123;</span><br><span class="line">    </span><br><span class="line">&#125;)</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">axios</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.interceptors = &#123;</span><br><span class="line">        request: <span class="keyword">new</span> interceptorsManner(),</span><br><span class="line">        response: <span class="keyword">new</span> interceptorsManner()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">interceptorsManner</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.handlers = [];</span><br><span class="line">&#125;</span><br><span class="line">interceptorsManner.prototype.use(<span class="function">(<span class="params">fullfilled, rejected</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">this</span>.handler.push(&#123;</span><br><span class="line">        fullfilled: fullfilled;</span><br><span class="line">        rejected: rejectes;</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>


      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/07/06/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93/%E4%B8%80%E5%8F%A5%E8%AF%9D%E5%9B%9E%E7%AD%94JS%E9%97%AE%E9%A2%98/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/07/06/%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93/%E4%B8%80%E5%8F%A5%E8%AF%9D%E5%9B%9E%E7%AD%94JS%E9%97%AE%E9%A2%98/" class="post-title-link" itemprop="url">一句化回答JS问题</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-07-06 21:28:16" itemprop="dateCreated datePublished" datetime="2021-07-06T21:28:16+08:00">2021-07-06</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-05 16:13:56" itemprop="dateModified" datetime="2021-11-05T16:13:56+08:00">2021-11-05</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E5%89%8D%E7%AB%AF%E9%A1%B5%E9%9D%A2/" itemprop="url" rel="index"><span itemprop="name">前端页面</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近一直在参加春招实习面试，面试过程中发现，有时候有些知识虽然看了很多的内容，看过很多深入原理的技术博客解释，但在面试官询问时，往往因为看得太多而不知道该如何抓住重点去回答问题，同时也只看了别人的理解，没办法很流畅通俗地用自己的话语将其完整、流畅地表达出来。emmm，正是因为这个原因，所以萌生了写一篇自己理解的想法，主要还是为了让自己能对每个问题都有自己的认知和总结吧。首先希望能用尽可能简洁的语言来陈述自己的理解，后面再对整个知识点进行解释吧。大概会连续水很多篇吧，根据面试时问题的方向来进行分类，只记录自己曾遇到的，或者特别常见的问题吧。第一篇就是最重要的JavaScript吧。</p>
<h2 id="1、JS的数据类型"><a href="#1、JS的数据类型" class="headerlink" title="1、JS的数据类型"></a>1、JS的数据类型</h2><p><strong>JS的数据类型主要分为两类吧；一是基础类型也就是值类型，二是引用类型。基础类型主要有：String、Number、Boolean、Null、Undefined、Symbol(ES6新增)。引用类型基本都是对象Object的引申吧，包括Object、Function、Date、Array、RegExp；Map、Set是ES6新增的数据结构。</strong></p>
<p>知识点扩充：基本类型保存再栈中，值不可变、但可以重新赋值，是值之间的比较。引用类型保存再堆中，值可变，是引用的比较。这里往往会引出深浅拷贝的问题，因为不用拷贝来进行复制对象时，由于传递的是值的引用，因此指向的是同一对象。</p>
<h2 id="2、深拷贝、浅拷贝"><a href="#2、深拷贝、浅拷贝" class="headerlink" title="2、深拷贝、浅拷贝"></a>2、深拷贝、浅拷贝</h2><p><strong>深浅拷贝都是对引用类型的复制。浅拷贝只拷贝一层，对于深层次的对象级别，仍然会拷贝引用；而深拷贝能够拷贝多层次的嵌套对象。</strong></p>
<p>知识点扩充：浅拷贝的方法，很简单明了，一层复制即可。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">simpleCopy</span> (<span class="params">obj1</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> obj2 = <span class="built_in">Array</span>.isArray(obj1) ? [] : &#123;&#125;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i <span class="keyword">in</span> obj1) &#123;</span><br><span class="line">        obj2[i] = obj1[i]</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> obj2</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>深拷贝的方法：1、JSON.parse(JSON.stringify());先转换成JSON对象，再转换回来，也很简单明了，但有些情况不适用。</p>
<p>2、循环递归调用即可。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">deepClone</span>(<span class="params">obj</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> objClone = <span class="built_in">Array</span>.isArray(obj) ? [] : &#123;&#125;;</span><br><span class="line">    <span class="keyword">if</span> (obj &amp;&amp; <span class="keyword">typeof</span> obj === <span class="string">"object"</span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (key <span class="keyword">in</span> obj) &#123;</span><br><span class="line">            <span class="keyword">if</span> (obj.hasOwnProperty(key)) &#123;</span><br><span class="line">                <span class="comment">//用for..in循环会获取原型链上的可枚举属性，因此需要再用hasOwnProperty来过滤</span></span><br><span class="line">                <span class="keyword">if</span> (obj[key] &amp;&amp; <span class="keyword">typeof</span> obj[key] === <span class="string">'object'</span>) &#123;</span><br><span class="line">                    objectClone[key] = deepClone(obj[key]);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    objClone[key] = obj[key];</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> objClone;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="3、判断数据类型"><a href="#3、判断数据类型" class="headerlink" title="3、判断数据类型"></a>3、判断数据类型</h2><p><strong>一般使用typeof、instanceof，或者使用object.tostring方法，从而判断数据类型。</strong></p>
<p>typeof 是一个操作符，其右侧跟一个一元表达式，并返回这个表达式的数据类型。对于null、array不能判断。</p>
<p>instanceof 可以正确判断对象的数据类型，用来判断 A 是否为 B 的实例;内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。</p>
<p>toString()：是 Object 的原型方法，调用该方法，默认返回当前对象的 [[Class]] 。</p>
<p>这是一个内部属性，其格式为 [object xxx] ，其中 xxx 就是对象的类型。对于 Object 对象，直接调用 toString() 就能返回 [object Object] 。而对于其他对象，则需要通过 call / apply 来调用才能返回正确的类型信息。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">foo</span>(<span class="params"></span>)</span>&#123;&#125;;</span><br><span class="line"><span class="built_in">Object</span>.prototype.toString.call(<span class="number">1</span>);  <span class="comment">//'[object Number]'</span></span><br><span class="line"><span class="built_in">Object</span>.prototype.toString.call(<span class="string">'1'</span>); <span class="comment">//'[object String]'</span></span><br><span class="line"><span class="built_in">Object</span>.prototype.toString.call(<span class="literal">NaN</span>); <span class="comment">//'[object Number]'</span></span><br><span class="line"><span class="built_in">Object</span>.prototype.toString.call(foo);  <span class="comment">//'[object Function]'</span></span><br><span class="line"><span class="built_in">Object</span>.prototype.toString.call([<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]); <span class="comment">//'[object Array]'</span></span><br><span class="line"><span class="built_in">Object</span>.prototype.toString.call(<span class="literal">undefined</span>); <span class="comment">//'[object Undefined]'</span></span><br><span class="line"><span class="built_in">Object</span>.prototype.toString.call(<span class="literal">null</span>); <span class="comment">//'[object Null]'</span></span><br><span class="line"><span class="built_in">Object</span>.prototype.toString.call(<span class="literal">true</span>); <span class="comment">//'[object Boolean]'</span></span><br></pre></td></tr></table></figure>

<h2 id="4、数组常见API"><a href="#4、数组常见API" class="headerlink" title="4、数组常见API"></a>4、数组常见API</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> arr3 = arr1.concat(arr2);<span class="comment">//合并且返回副本</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> bar = [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>];</span><br><span class="line"><span class="built_in">Array</span>.from(bar);<span class="comment">//from()方法返回一个新的数组迭代器对象，该对象包含数组中每个索引的键/值对</span></span><br><span class="line"><span class="keyword">let</span> dp = <span class="built_in">Array</span>.from(<span class="keyword">new</span> <span class="built_in">Array</span>(length1), ()=&gt; &#123;</span><br><span class="line">        <span class="keyword">new</span> <span class="built_in">Array</span>(length2).fill(<span class="number">0</span>);</span><br><span class="line">    &#125;);<span class="comment">//快速定义二维数组</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//find()方法返回满足回调条件的数组中第一个元素的值。否则将返回未定义</span></span><br><span class="line"><span class="comment">//forEach()方法遍历数组,foreach中this指向为undefine</span></span><br><span class="line"><span class="comment">//indexOf()；includes()；均用于查询数组中是否包含某个元素</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//join()将数组中的所有元素(或类似数组的对象)连接到一个字符串中，默认为逗号</span></span><br><span class="line"><span class="keyword">var</span> a = [<span class="string">'Wind'</span>, <span class="string">'Rain'</span>, <span class="string">'Fire'</span>];</span><br><span class="line">a.join();    <span class="comment">// 'Wind,Rain,Fire'</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//splice()方法通过删除现有元素和/或添加新元素来更改数组的内容。</span></span><br><span class="line">array.splice(start, deleteCount, item1, item2);</span><br><span class="line">myFish.splice(<span class="number">2</span>, <span class="number">1</span>); <span class="comment">// remove 1 item at 2-index position (that is, "drum")</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//slice()切片</span></span><br><span class="line"><span class="built_in">Array</span>.prototype.slice();<span class="comment">//取出任意元素，参数一：从哪开始；参数二：(可选)结束位置。</span></span><br></pre></td></tr></table></figure>

<h2 id="5、bind、call、apply的区别"><a href="#5、bind、call、apply的区别" class="headerlink" title="5、bind、call、apply的区别"></a>5、bind、call、apply的区别</h2><p><strong>相同点：均用于改变函数的上下文，即this指向。</strong></p>
<p>fn.bind： 不会立即调用，而是返回一个绑定后的新函数。fn.call：立即调用，返回函数执行结果，this指向第一个参数，后面可有多个参数，并且这些都是fn函数的参数。fn.apply：立即调用，返回函数的执行结果，this指向第一个参数，第二个参数是个数组，这个数组里内容是fn函数的参数。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sub</span>(<span class="params">a, b</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> sub = a + b</span><br><span class="line">    <span class="built_in">console</span>.log(sub)</span><br><span class="line">    <span class="keyword">return</span> sub</span><br><span class="line">&#125;</span><br><span class="line">sub.call(<span class="literal">null</span>, <span class="number">1</span>, <span class="number">2</span>)</span><br><span class="line">sub.apply(<span class="literal">null</span>, [<span class="number">1</span>, <span class="number">2</span>]) </span><br><span class="line">sub.bind(<span class="literal">null</span>, <span class="number">1</span>, <span class="number">2</span>)()</span><br></pre></td></tr></table></figure>

<h2 id="6、new的原理"><a href="#6、new的原理" class="headerlink" title="6、new的原理"></a>6、new的原理</h2><p><strong>1、创建一个新对象；2、将构造函数与新对象在原型链中联系起来，即新对象的proto要指向构造函数的原型对象；3、执行构造函数，并将构造函数的初始属性都挂载在新对象上。</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">_new</span> (<span class="params">constructor, ...args</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> obj = &#123;&#125;;</span><br><span class="line">    obj._proto_ = <span class="keyword">constructor</span>.prototype;</span><br><span class="line">    let result = <span class="keyword">constructor</span>.call(obj, ...args);</span><br><span class="line">    //在obj中执行构造函数,并将构造函数的属性挂载到对象上；</span><br><span class="line">    return (typeof result === 'object') ? result : obj;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="7、如何正确判断this"><a href="#7、如何正确判断this" class="headerlink" title="7、如何正确判断this"></a>7、如何正确判断this</h2><p><strong>核心原理：this的指向在函数定义的时候是确定不了的，只有函数执行的时候才能确定，this最终指向调用它的对象；同样，不管有多少层级的调用，this也只是指向它上一级的对象。</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//1、不管多少层调用，指向调用时的上一层对象。</span></span><br><span class="line"><span class="keyword">var</span> flora = &#123;</span><br><span class="line">    Animal:<span class="string">"a"</span>,</span><br><span class="line">    mars:&#123;</span><br><span class="line">        Animal:<span class="string">"b"</span>,</span><br><span class="line">        say:<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">            <span class="built_in">console</span>.log(<span class="keyword">this</span>.Animal);</span><br><span class="line">    	&#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br><span class="line">flora.mars.say();<span class="comment">//输出为b</span></span><br><span class="line"><span class="comment">//2、定义时不决定this，指向调用时的对象</span></span><br><span class="line"><span class="keyword">var</span> flora1 = &#123;</span><br><span class="line">    Animal:<span class="string">"a"</span>,</span><br><span class="line">    mars:&#123;</span><br><span class="line">        Animal:<span class="string">"b"</span>,</span><br><span class="line">        say:<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">            <span class="built_in">console</span>.log(<span class="keyword">this</span>);</span><br><span class="line">            <span class="built_in">console</span>.log(<span class="keyword">this</span>.Animal);</span><br><span class="line">    	&#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> pink = flora.mars.say;</span><br><span class="line">pink();<span class="comment">//输出分别为window和undefine，因为pink()在全局环境上调用，因此this为window。</span></span><br><span class="line"><span class="comment">//3、new会改变this的指向</span></span><br><span class="line"><span class="comment">/*但当构造函数有返回值时，又不一样了，</span></span><br><span class="line"><span class="comment">如果返回的是一个对象：this会指向哪个返回的对象。</span></span><br><span class="line"><span class="comment">如果返回的不是一个对象：this还是指向那个函数的实例 */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">flora</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.animal = <span class="string">"a"</span>;   </span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> blue = <span class="keyword">new</span> flora();</span><br><span class="line"><span class="built_in">console</span>.log(blue.animal); <span class="comment">//a</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">flora</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.animal = <span class="string">"a"</span>;   </span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;&#125;;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> blue = <span class="keyword">new</span> flora();</span><br><span class="line"><span class="built_in">console</span>.log(blue.animal); <span class="comment">//undefined</span></span><br></pre></td></tr></table></figure>

<h2 id="8、闭包"><a href="#8、闭包" class="headerlink" title="8、闭包"></a>8、闭包</h2><p>闭包：有权访问另一个函数作用域中的变量的函数。<strong>闭包可以让一个函数访问并操作其声明时的作用域中的变量和函数，并且，即使声明时的作用域消失了，也可以调用</strong>。要注意的是：闭包不是在创建的那一时刻点的状态的快照，而是一个真实的封装，只要闭包存在，就可以对其进行修改。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//最常用的：1、封装私有变量：</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Ninja</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> feints = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">this</span>.getFeints = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> feints;<span class="comment">//在构造函数外部的代码无法访问feints变量，只能通过办读方式访问</span></span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">this</span>.feint = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        feints++;<span class="comment">//声明一个累加方法，由于feints为私有变量，因此在外部无法累加</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">var</span> ninja1 = <span class="keyword">new</span> Ninja();</span><br><span class="line"><span class="built_in">console</span>.log(ninja1.feints === <span class="literal">undefined</span>);<span class="comment">//true，验证我们无法直接获取该变量值</span></span><br><span class="line">ninja1.feint();</span><br><span class="line"><span class="built_in">console</span>.log(ninja1.getFeints() === <span class="number">1</span>);<span class="comment">//true, 可通过getFeints方法操作该变量值</span></span><br><span class="line"><span class="keyword">let</span> ninja2 = <span class="keyword">new</span> Ninja();</span><br><span class="line"><span class="built_in">console</span>.log(ninja2.getFeints() === <span class="number">0</span>);<span class="comment">//true，ninja2对象具有自己私有的feints变量</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*2、回调函数：</span></span><br><span class="line"><span class="comment">指要在将来不确定的某一时刻异步调用的函数，在这种函数中，通常需要频繁访问外部数据。</span></span><br><span class="line"><span class="comment">在定时器、事件监听、Ajax请求、跨窗口通信、Web Workers或者任何异步中，只要使用了回调函数，实际上就是在使用闭包。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*3、闭包的副作用：(1)闭包会携带其函数的作用域，因此会占用更多的内存；</span></span><br><span class="line"><span class="comment">(2)闭包只能取得包含函数中任何变量的最后一个值；*/</span></span><br></pre></td></tr></table></figure>

<h2 id="9、prototype"><a href="#9、prototype" class="headerlink" title="9、prototype"></a>9、prototype</h2><p>显示原型：当你创建一个函数时，JS会为这个函数(别忘了：JS一切皆对象)自动添加prototype属性，这个属性的值是一个对象，也就是原型对象(即函数名.prototype)。显式原型（prototype）只有函数才拥有。我们后面讲的隐式原型则是所有对象都有。</p>
<p>隐式原型：JavaScript中任意对象都有一个内置属性[[prototype]]，在ES5之前没有标准的方法访问这个内置属性，但是大多数浏览器都支持通过__proto__来访问。现在，所谓的隐式原型就是__proto__ 了隐式原型指向创建这个对象的函数的prototype（Object.create函数构造出来的实例有点例为，后面会说明。JavaScript 可以通过prototype和__proto__在两个对象之间创建一个关联，使得一个对象就可以通过委托访问另一个对象的属性和函数。</p>
<h2 id="10、继承"><a href="#10、继承" class="headerlink" title="10、继承"></a>10、继承</h2><p><strong>使用原型链继承，继承了父类的属性和方法，但对于引用属性，由于传入的是其地址，因此多个指向的是同样的父类属性；使用构造函数来实现属性的继承，在子类中调用父类构造函数，从而现实例属性的继承，因此同样子类实例上的属性覆盖了父类的同名属性。</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent3</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.name = <span class="string">'parent3'</span>;</span><br><span class="line">    <span class="keyword">this</span>.play = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">  &#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child3</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    Parent3.call(<span class="keyword">this</span>);</span><br><span class="line">    <span class="keyword">this</span>.type = <span class="string">'child3'</span>;</span><br><span class="line">  &#125;</span><br><span class="line">Child3.prototype = <span class="keyword">new</span> Parent3();</span><br><span class="line"></span><br><span class="line"><span class="comment">/*寄生组合式继承：简单优化上面的两点：</span></span><br><span class="line"><span class="comment">1、避免parent构造函数执行2次；2、子类的构造函数应该指向Child，而不是Parent</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent5</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.name = <span class="string">'parent5'</span>;</span><br><span class="line">    <span class="keyword">this</span>.play = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">Child5</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    Parent5.call(<span class="keyword">this</span>);</span><br><span class="line">    <span class="keyword">this</span>.type = <span class="string">'child5'</span>;</span><br><span class="line">  &#125;</span><br><span class="line">Child5.prototype = <span class="built_in">Object</span>.create(Parent5.prototype);</span><br><span class="line">Child5.prototype.constructor = Child5;</span><br></pre></td></tr></table></figure>

<h2 id="11、防抖与节流"><a href="#11、防抖与节流" class="headerlink" title="11、防抖与节流"></a>11、防抖与节流</h2><p><strong>防抖：即短时间内大量触发同一事件，只会执行一次函数。原理：设置一个定时器，约定在xx毫秒之后再触发事件处理器，每次·触发事件都会重新设置处理器，直至xx毫秒内无第二次操作</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">debounce</span> (<span class="params">func, await</span>) </span>&#123;</span><br><span class="line">    <span class="comment">//传入参数分别为需要添加防抖的事件函数，和防抖的时间间隔</span></span><br><span class="line">    <span class="keyword">let</span> timeout = &#123;&#125;;</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">let</span> context = <span class="keyword">this</span>;</span><br><span class="line">        <span class="keyword">let</span> args = <span class="built_in">arguments</span>;</span><br><span class="line">        <span class="keyword">if</span> (timeout) clearTimeout(timeout);<span class="comment">//每次触发都会重置定时器</span></span><br><span class="line">        timeout = setTimeout(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">            func.apply(context, args);</span><br><span class="line">        &#125;, <span class="keyword">await</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>节流：间隔执行，每隔一段时间就执行一次。原理：设置一个定时器，约定xx毫秒后执行时间，如果时间到了，那么执行函数并重置定时器，和防抖的区别是：防抖每次触发事件都重置定时器，而节流在定时器到事件后再清空。</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">throttle</span>(<span class="params">func, wait</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> timeout = <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">let</span> context = <span class="keyword">this</span>;</span><br><span class="line">        <span class="keyword">let</span> args = <span class="built_in">arguments</span>;</span><br><span class="line">        <span class="keyword">if</span> (!timeout) &#123;</span><br><span class="line">            timeout = setTimeout(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">                timeout = <span class="literal">null</span>;</span><br><span class="line">                func.apply(context, args);</span><br><span class="line">            &#125;, wait);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="12、Array-sort"><a href="#12、Array-sort" class="headerlink" title="12、Array.sort()"></a>12、Array.sort()</h2><p>默认情况下，sort()方法按升序排列数组项；且会调用每个数组项的toString()方法，比较字符串；因此直接用于数字比较不会正确。sort()可以接受一个函数作为参数，该函数可以接收两个参数，1、第一个参数应该位于第二个之前，则返回负数；</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> values = [<span class="number">0</span>, <span class="number">1</span>, <span class="number">5</span>, <span class="number">10</span>, <span class="number">15</span>];</span><br><span class="line">values.sort();</span><br><span class="line">alert(values);<span class="comment">//0, 1, 10 ,15 ,5 字符串比较时，10在5的前面，因此数组顺序被改变</span></span><br><span class="line">values.sort(<span class="function"><span class="keyword">function</span> <span class="title">compare</span> (<span class="params">value1, value2</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> value1 - value2;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">//当value1小于value2时，返回负数，小的放在前面，因此为升序排列。</span></span><br></pre></td></tr></table></figure>

<p>原理思路：基本上使用的是快速排序的方法。设要排序的元素个数是n：1、当 n &lt;= 10 时，采用插入排序；2、当 n &gt; 10 时，采用三路快速排序；3、10 &lt; n &lt;= 1000, 采用中位数作为哨兵元素；4、n &gt; 1000, 每隔 200~215 个元素挑出一个元素，放到一个新数组，然后对它排序，找到中间位置的数，以此作为中位数。</p>
<p>为什么要花这么大的力气选择哨兵元素？因为快速排序的性能瓶颈在于递归的深度，最坏的情况是每次的哨兵都是最小元素或者最大元素，那么进行partition(一边是小于哨兵的元素，另一边是大于哨兵的元素)时，就会有一边是空的，那么这么排下去，递归的层数就达到了n, 而每一层的复杂度是O(n)，因此快排这时候会退化成O(n^2)级别。</p>
<h2 id="13、Ajax的请求过程"><a href="#13、Ajax的请求过程" class="headerlink" title="13、Ajax的请求过程"></a>13、Ajax的请求过程</h2><p><strong>我们常用的 ajax 就是通过 XMLHttpRequest 对象实现的，这个对象有很多的属性和事件，在使用之前，我们需要先将它实例化.实例化后，我们就可以通过 xhr 来发起一个请求。</strong></p>
<p>xhr 具有一个 open 方法，这个方法的作用类似于初始化，并不会发起真正的请求；open 方法具有 5 个参数，但是常用的是前 3 个method： 请求方式 —— get / post；url：请求的地址；async：是否异步请求，默认为 true（异步）。send 方法发送请求，并接受一个可选参数，当请求方式为 post 时，可以将请求体的参数传入，当请求方式为 get 时，可以不传或传入 null，不管是 get 还是 post，参数都需要通过 encodeURIComponent 编码后拼接。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">ajax</span>(<span class="params">url, fnSucc, fnFaild</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> xhttp;</span><br><span class="line">    <span class="comment">// 第一步：创建XMLHttpRequest对象</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">window</span>.XMLHttpRequest) &#123;</span><br><span class="line">        <span class="comment">// 现代浏览器</span></span><br><span class="line">        xhttp = <span class="keyword">new</span> XMLHttpRequest();</span><br><span class="line">     &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// IE6等老版本浏览器</span></span><br><span class="line">        xhttp = <span class="keyword">new</span> ActiveXObject(<span class="string">"Microsoft.XMLHTTP"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 第二步：调用XMLHttpRequest的open函数  </span></span><br><span class="line">    xhttp.open(<span class="string">'GET'</span>, url);</span><br><span class="line">    <span class="comment">// 第三步：XMLHttpRequest向服务器发送请求</span></span><br><span class="line">    xhttp.send();</span><br><span class="line">    <span class="comment">// 第四步：处理响应</span></span><br><span class="line">    xhttp.onreadystatechange = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"><span class="comment">//XMLHttpRequest上有一个方法，名叫onreadystatechange，当请求接收到响应的时候便会触发，在这个方法内部，我们来定义对响应的处理：</span></span><br><span class="line">      <span class="keyword">if</span> (xhttp.readyState === <span class="number">4</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (xhttp.status === <span class="number">200</span>) &#123;</span><br><span class="line">          fnSucc(xhttp.responseText)</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (fnFaild) fnFaild(xhttp.responseText);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="14、JS垃圾回收机制"><a href="#14、JS垃圾回收机制" class="headerlink" title="14、JS垃圾回收机制"></a>14、JS垃圾回收机制</h2><p>周期回收：周期性执行，找出那些不在继续使用的变量，然后释放其内存；标记清除：是当变量进入环境时，将这个变量标记为“进入环境”。当变量离开环境时，则将其标记为“离开环境”。标记“离开环境”的就回收内存。</p>
<h2 id="15、addEventListener"><a href="#15、addEventListener" class="headerlink" title="15、addEventListener"></a>15、addEventListener</h2><p><strong>target.addEventListener(eventType,callback,useCapture)；<code>useCapture</code>：默认为false，在DOM树中冒泡阶段执行回调，设置为true时则在捕获阶段执行回调</strong></p>
<p><strong>事件委托，通俗地来讲，就是把一个元素响应事件（click、keydown……）的函数委托到另一个元素。一般来讲，会把一个或者一组元素的事件委托到它的父层或者更外层元素上，真正绑定事件的是</strong>外层元素，当事件响应到需要绑定的元素上时，会通过事件冒泡机制从而触发它的外层元素的绑定事件上，然后在外层元素上去执行函数。</p>
<p>在 <strong>document.addEventListener</strong> 的时候我们可以<strong>设置事件模型</strong>：事件冒泡、事件捕获，一般来说都是用事件冒泡的模型；捕获阶段：在事件冒泡的模型中，捕获阶段不会响应任何事件； 目标阶段：目标阶段就是指事件响应到触发事件的最底层元素上； 冒泡阶段：冒泡阶段就是事件的触发响应会从最底层目标一层层地向外到最外层（根节点），事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层。</p>
<p> focus、blur 之类的事件本身没有事件冒泡机制，所以无法委托； mousemove、mouseout这样的事件，虽然有事件冒泡，但是只能不断通过位置去计算定位，对性能消耗高，因此也是不适合于事件委托的。</p>
<h2 id="16、weakMap与Map"><a href="#16、weakMap与Map" class="headerlink" title="16、weakMap与Map"></a>16、weakMap与Map</h2><p>WeakMap 对象是一组键/值对的集合，其中的键是弱引用的。其键必须是对象，而值可以是任意的。</p>
<p>什么是<strong>弱引用</strong>呢？</p>
<p>在计算机程序设计中，弱引用与强引用相对，是指不能确保其引用的对象不会被垃圾回收器回收的引用。 一个对象若只被弱引用所引用，则被认为是不可访问（或弱可访问）的，并因此可能在任何时刻被回收。</p>
<p>我们默认创建一个对象：<code>const obj = {}</code>，就默认创建了一个强引用的对象，我们只有手动将<code>obj = null</code>，它才会被垃圾回收机制进行回收，如果是弱引用对象，垃圾回收机制会自动帮我们回收。</p>
<p>如果我们使用<code>Map</code>的话，那么对象间是存在强引用关系的：虽然我们手动将<code>obj</code>，进行释放，然是<code>target</code>依然对<code>obj</code>存在强引用关系，所以这部分内存依然无法被释放。</p>
<p>如果是<code>WeakMap</code>的话，<code>target</code>和<code>obj</code>存在的就是弱引用关系，当下一次垃圾回收机制执行时，这块内存就会被释放掉。</p>
<p>设想一下，如果我们要拷贝的对象非常庞大时，使用<code>Map</code>会对内存造成非常大的额外消耗，而且我们需要手动清除<code>Map</code>的属性才能释放这块内存，而<code>WeakMap</code>会帮我们巧妙化解这个问题。从而避免了内存泄漏的问题。</p>
<h2 id="17、原型与原型链"><a href="#17、原型与原型链" class="headerlink" title="17、原型与原型链"></a>17、原型与原型链</h2><p>在JavaScript中，每当定义一个函数数据类型(普通函数、类)时候，都会天生自带一个prototype属性，这个属性指向函数的原型对象。当函数经过new调用时，这个函数就成为了构造函数，返回一个全新的实例对象，这个实例对象有一个__proto__属性，指向构造函数的原型对象。</p>
<h2 id="18、JS类型转换机制"><a href="#18、JS类型转换机制" class="headerlink" title="18、JS类型转换机制"></a>18、JS类型转换机制</h2><p><strong>JS中，类型转换只有三种：1、转换成数字。2、转换成布尔值。3、转换成字符串。</strong></p>
<p>这里其实有一个经典的问题，就是“JS的**[]==![]和{} ==!{}**”，两者的结果分别是多少？</p>
<p>原则：<br>1.两者类型是否相同，相同就比大小<br>2.类型不相同，进行类型转换<br>3.判断是否在对比 null 和 undefined，是的话就会返回 true<br>4.判断两者类型是否为 string 和 number，是的话就会将字符串转换为 number<br>5.判断其中一方是否为 boolean，是的话就会把 boolean 转为 number 再进行判断<br>6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol，是的话就会把 object 转为原始类型再进行判断</p>
<p><strong>比较[]==![]：</strong><br>1.“!”运算符优先于“==”，优先转化“![]”<br>2.![]-&gt;!true-&gt;false（一元“！”运算符将其操作数转化为布尔值并取反）<br>3.[]==false（一方为 boolean，把 boolean 转为 number 再进行判断）<br>4.[]==0（一方为 object 且另一方为 number，把 object 转为原始类型再进行判断）<br>5.[]-&gt;””(对象转化为原始值,首先调用 valueOf() 方法只能得到本身,则调用 toString() 方法）<br>6.””==0（string 和 number比较，将字符串转换为 number）<br>7.0==0：true</p>
<p><strong>比较{}==!{}：</strong><br>1.“!”运算符优先于“==”，优先转化“!{}”<br>2.!{}-&gt;!true-&gt;false（一元“！”运算符将其操作数转化为布尔值并取反）<br>3.{}==false（一方为 boolean，把 boolean 转为 number 再进行判断）<br>4.{}==0（一方为 object 且另一方为 number，把 object 转为原始类型再进行判断）<br>5.{}-&gt;”[object Object]”(对象转化为原始值,首先调用 valueOf() 方法只能得到本身,则调用 toString() 方法）<br>6.”[object Object]”==0（string 和 number比较，将字符串转换为 number）<br>7.NaN==0：false</p>
<h2 id="19、JavaScript的执行上下文"><a href="#19、JavaScript的执行上下文" class="headerlink" title="19、JavaScript的执行上下文"></a>19、JavaScript的执行上下文</h2><p>在JavaScript中有三种代码运行环境：</p>
<ul>
<li>Global Code<ul>
<li>JavaScript代码开始运行的默认环境</li>
</ul>
</li>
<li>Function Code<ul>
<li>代码进入一个JavaScript函数</li>
</ul>
</li>
<li>Eval Code<ul>
<li>使用eval()执行代码</li>
</ul>
</li>
</ul>
<p>为了表示不同的运行环境，JavaScript中有一个<strong>执行上下文（Execution context，EC）</strong>的概念。也就是说，当JavaScript代码执行的时候，会进入不同的执行上下文，这些执行上下文就构成了一个<strong>执行上下文栈（Execution context stack，ECS）</strong>。</p>
<p>当JavaScript代码执行的时候，第一个进入的总是默认的Global Execution Context，所以说它总是在ECS的最底部。</p>
<p>对于每个Execution Context都有三个重要的属性，<strong>变量对象（Variable object，VO），作用域链（Scope chain）和this</strong>。这三个属性跟代码运行的行为有很重要的关系，下面会一一介绍。执行上下文决定了三个属性：变量对象、作用域链、this。</p>
<p><strong>变量对象</strong>：变量对象是与执行上下文相关的数据作用域。它是一个与上下文相关的特殊对象，其中存储了在上下文中定义的变量和函数声明。它会包含以下信息：变量、函数声明、函数的形参。</p>
<p><strong>活动对象</strong>：在函数执行上下文中，VO是不能直接访问的，此时由激活对象(Activation Object,缩写为AO)扮演VO的角色。激活对象 是在进入函数上下文时刻被创建的，它通过函数的arguments属性初始化。</p>
<p>Arguments Objects 是函数上下文里的激活对象AO中的内部对象，它包括下列属性：callee：指向当前函数的引用；length： 真正传递的参数的个数；properties-indexes：就是函数的参数值(按参数列表从左到右排列)。</p>
<p>对于VO和AO的关系可以理解为，VO在不同的Execution Context中会有不同的表现：当在Global Execution Context中，可以直接使用VO；但是，在函数Execution Context中，AO就会被创建，在函数执行上下文中，就会创建出活动对象。</p>
<p><strong>执行上下文的创建阶段</strong>：当一段JavaScript代码执行的时候，JavaScript解释器会创建Execution Context，其实这里会有两个阶段：</p>
<ul>
<li>创建阶段（当函数被调用，但是开始执行函数内部代码之前）<ul>
<li>创建Scope chain</li>
<li>创建VO/AO（variables, functions and arguments）</li>
<li>设置this的值</li>
</ul>
</li>
<li>激活/代码执行阶段<ul>
<li>设置变量的值、函数的引用，然后解释/执行代码</li>
</ul>
</li>
</ul>
<p>这里想要详细介绍一下”创建VO/AO”中的一些细节，因为这些内容将直接影响代码运行的行为。</p>
<p>对于”创建VO/AO”这一步，JavaScript解释器主要做了下面的事情：</p>
<ul>
<li>根据函数的参数，创建并初始化arguments object</li>
<li>扫描函数内部代码，查找函数声明（Function declaration）<ul>
<li>对于所有找到的函数声明，将函数名和函数引用存入VO/AO中。对于函数声明，则会立即将引入存进V0/AO中，此时便可以显示正常结果。</li>
<li><strong>如果VO/AO中已经有同名的函数，那么就进行覆盖</strong></li>
</ul>
</li>
<li>扫描函数内部代码，查找变量声明（Variable declaration）<ul>
<li>对于所有找到的变量声明，将变量名存入VO/AO中，并初始化为”undefined”。（定义的变量会先将其初始化为undefined值）</li>
<li>如果变量名称跟已经声明的形式参数或函数相同，则变量声明不会干扰已经存在的这类属性</li>
</ul>
</li>
</ul>
<p><strong>实例分析</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(bar);</span><br><span class="line">    <span class="built_in">console</span>.log(baz);</span><br><span class="line">    <span class="keyword">var</span> bar = <span class="number">20</span>;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">baz</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"baz"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;)()</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">代码解释：匿名函数会首先进入"创建阶段"，JavaScript解释器会创建一个"Function Execution Context"，然后创建Scope chain，VO/AO和this。根据前面的介绍，解释器会扫描函数和变量声明，如下的AO会被创建：</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">因此输出结果：</span></span><br><span class="line"><span class="comment">	undefined；</span></span><br><span class="line"><span class="comment">	function baz()&#123; console.log("baz"); &#125;</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(bar);<span class="comment">//这个会报错，is not defined</span></span><br><span class="line">    <span class="built_in">console</span>.log(baz);</span><br><span class="line">    </span><br><span class="line">    bar = <span class="number">20</span>;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="built_in">window</span>.bar);</span><br><span class="line">    <span class="built_in">console</span>.log(bar);</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">baz</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"baz"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;)()</span><br><span class="line"><span class="comment">/*同理，很简单，</span></span><br><span class="line"><span class="comment">因此输出结果：</span></span><br><span class="line"><span class="comment">	function baz()&#123; console.log("baz"); &#125;</span></span><br><span class="line"><span class="comment">	20</span></span><br><span class="line"><span class="comment">	20</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(foo);</span><br><span class="line">    <span class="built_in">console</span>.log(bar);</span><br><span class="line">    <span class="built_in">console</span>.log(baz);</span><br><span class="line">    <span class="keyword">var</span> foo = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;&#125;;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">bar</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"bar"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">var</span> bar = <span class="number">20</span>;</span><br><span class="line">    <span class="built_in">console</span>.log(bar);</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">baz</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"baz"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)()</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">最后这个才是重头戏，</span></span><br><span class="line"><span class="comment">代码的输出结果为：</span></span><br><span class="line"><span class="comment">	undefined</span></span><br><span class="line"><span class="comment">	function bar()&#123; console.log("bar"); &#125;</span></span><br><span class="line"><span class="comment">	function baz()&#123; console.log("baz"); &#125;</span></span><br><span class="line"><span class="comment">	20</span></span><br><span class="line"><span class="comment">	</span></span><br><span class="line"><span class="comment">代码中，最"奇怪"的地方应该就是"bar"的输出了，第一次是一个函数，第二次是"20"。</span></span><br><span class="line"><span class="comment">其实也很好解释，回到前面对"创建VO/AO"的介绍，在创建VO/AO过程中，解释器会先扫描函数声明，然后"foo: &lt;function&gt;"就被保存在了AO中；但解释器扫描变量声明的时候，虽然发现"var bar = 20;"，但是因为"foo"在AO中已经存在，所以就没有任何操作了。</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">但是，当代码执行到第二句"console.log(bar);"的时候，"激活/代码执行阶段"已经把AO中的"bar"重新设置了。</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<h2 id="20、JavaScript的作用域链"><a href="#20、JavaScript的作用域链" class="headerlink" title="20、JavaScript的作用域链"></a>20、JavaScript的作用域链</h2><p>在很多语言中，作用域都是通过代码块{}来决定的，但是JavaScript中作用域是跟函数相关的。一般分为全局作用域与函数作用域。</p>
<p><strong>全局作用域</strong>中的对象可以在代码的任何地方访问，一般来说，下面情况的对象会在全局作用域中：</p>
<ul>
<li>最外层函数和在最外层函数外面定义的变量    </li>
<li>没有通过关键字”var”声明的变量</li>
<li>浏览器中，window对象的属性</li>
</ul>
<p><strong>局部作用域</strong>又被称为函数作用域（Function scope），所有的变量和函数只能在作用域内部使用。</p>
<p>通过前面一篇文章了解到，每一个Execution Context中都有一个VO，用来存放变量，函数和参数等信息。在JavaScript代码运行中，所有用到的变量都需要去当前AO/VO中查找，当找不到的时候，就会继续查找上层Execution Context中的AO/VO。这样一级级向上查找的过程，就是所有Execution Context中的AO/VO组成了一个作用域链。</p>
<p>所以说，<strong>作用域链</strong>与一个执行上下文相关，是内部上下文所有变量对象（包括父变量对象）的列表，用于变量查询，这就是原型链与作用域链最为不同的地方。</p>
<p><strong>闭包：</strong>在JavaScript中，闭包跟作用域链有紧密的关系。相信大家对下面的闭包例子一定非常熟悉，代码中通过闭包实现了一个简单的计数器。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">counter</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> x = <span class="number">0</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        increase: <span class="function"><span class="keyword">function</span> <span class="title">increase</span>(<span class="params"></span>) </span>&#123; <span class="keyword">return</span> ++x; &#125;,</span><br><span class="line">        decrease: <span class="function"><span class="keyword">function</span> <span class="title">decrease</span>(<span class="params"></span>) </span>&#123; <span class="keyword">return</span> --x; &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> ctor = counter();</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(ctor.increase());</span><br><span class="line"><span class="built_in">console</span>.log(ctor.decrease());</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">counter Execution Context退出了执行上下文栈，但是counter AO没有销毁，可以继续访问。</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<p><strong>作用域链查找：</strong></p>
<p>作用域链（scope chain）的主要作用就是用来进行变量查找。但是，在JavaScript中还有原型链（prototype chain）的概念。</p>
<p>由于作用域链和原型链的相互作用，这样就形成了一个二维的查找。</p>
<p>对于这个二维查找可以总结为：<strong>当代码需要查找一个属性（property）或者描述符（identifier）的时候，首先会通过作用域链（scope chain）来查找相关的对象；一旦对象被找到，就会根据对象的原型链（prototype chain）来查找属性（property）</strong>。</p>
<h2 id="21、JS函数值传递问题"><a href="#21、JS函数值传递问题" class="headerlink" title="21、JS函数值传递问题"></a>21、JS函数值传递问题</h2><h3 id="怎么理解JS函数参数是按值传递"><a href="#怎么理解JS函数参数是按值传递" class="headerlink" title="怎么理解JS函数参数是按值传递"></a>怎么理解JS函数参数是按值传递</h3><p>我们先来看一下这道题：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a=[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>];</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">b</span>)</span>&#123;</span><br><span class="line">    b=[<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>];</span><br><span class="line">&#125;</span><br><span class="line">fn(a);</span><br><span class="line"><span class="built_in">console</span>.log(a); <span class="comment">//请问a 输出多少</span></span><br></pre></td></tr></table></figure>

<p>我们接下来再看一道题：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> obj=&#123;</span><br><span class="line">    name:<span class="string">'大雄'</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">b</span>)</span>&#123;</span><br><span class="line">    b.name=<span class="string">'小熊'</span></span><br><span class="line">&#125;</span><br><span class="line">fn(obj);</span><br><span class="line"><span class="built_in">console</span>.log(obj.name); <span class="comment">// 输出多少</span></span><br></pre></td></tr></table></figure>

<hr>
<p>第一道题答案：[1,2,3];</p>
<p>第二道题答案：小熊；</p>
<p>怎么样你都答对了吗？</p>
<p>接下来我们详解一下为什么</p>
<p>《Javascript 高级程序设计》第三版中有这么一句话 <strong>ECMAScript中所有函数的参数都是按值传递的</strong></p>
<p>但是从第二道题我们明显感觉到：就是按照引用传递的啊；首先我们都知道，js 中的类型分为 值类型和引用类型，值类型保存在内存栈中，引用类型保存在内存堆中；</p>
<p>我们看一下 下面的这段代码：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a=&#123;</span><br><span class="line">  name:<span class="string">'大雄'</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">var</span> b=a;</span><br><span class="line">b.name=<span class="string">'小熊'</span>;</span><br><span class="line"><span class="built_in">console</span>.log(a.name);</span><br></pre></td></tr></table></figure>

<p>这段代码的执行结果想必大家都知道，因为a 保存的是一个堆指针，b赋值后指向了同一个堆内存；</p>
<p><img src="/../../image/%E4%B8%80%E5%8F%A5%E8%AF%9D%E5%9B%9E%E7%AD%94JS%E9%97%AE%E9%A2%98/v2-0077e57c58c08c97d8010db0d1ec2d8d_720w.jpg" alt="img"></p>
<p>我们现在理解下：函数都是按值传递的；</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a=&#123;&#125;;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">b</span>)</span>&#123;</span><br><span class="line">    b=<span class="number">2</span>;</span><br><span class="line">&#125;</span><br><span class="line">fn(a);</span><br><span class="line"><span class="comment">//这段代码中函数的参数传递相当于如下代码</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">b</span>)</span>&#123;</span><br><span class="line">  <span class="keyword">var</span> b=a; <span class="comment">// 对参数进行了复制；在栈内存在开辟了空间；</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>所以说：<strong>值传递</strong> 可以理解为复制变量值，基本类型复制后俩个变量完全独立；引用类型复制的是引用（即指针），之后的任何一方改变都会映射到另一方；</p>
<p>我相信说到这里第二道题的答案大家应该都明白了，接下来我们看一下第一道题：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 第一题 相当于如下的代码</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">b</span>)</span>&#123;</span><br><span class="line">    <span class="keyword">var</span> b=a; <span class="comment">// b与a 指向了同一个堆内存；</span></span><br><span class="line">  b=[<span class="number">4</span>,<span class="number">5</span>,<span class="number">6</span>]; <span class="comment">// 并没有改变指针的内容；而是开辟了一个新的堆空间；所以a,b 就不相互影响了； </span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>我们用图说明一下：</p>
<p><img src="/../../image/%E4%B8%80%E5%8F%A5%E8%AF%9D%E5%9B%9E%E7%AD%94JS%E9%97%AE%E9%A2%98/v2-97a63d2d5761c188ae32b84174e7d2e9_720w.jpg" alt="img"></p>
<p>结合图和代码，我们对程序的执行一目了然，相信大家都明白了。</p>
<p>所以说js中<strong>函数的参数都是按值传递的</strong></p>
<h2 id="22、用var来实现let"><a href="#22、用var来实现let" class="headerlink" title="22、用var来实现let"></a>22、用var来实现let</h2><h2 id="23、JS中作用域链的实现"><a href="#23、JS中作用域链的实现" class="headerlink" title="23、JS中作用域链的实现"></a>23、JS中作用域链的实现</h2><h2 id="24、JS引用类型与值类型存储地址不同"><a href="#24、JS引用类型与值类型存储地址不同" class="headerlink" title="24、JS引用类型与值类型存储地址不同"></a>24、JS引用类型与值类型存储地址不同</h2><h2 id="25、如何判断数组数据类型"><a href="#25、如何判断数组数据类型" class="headerlink" title="25、如何判断数组数据类型"></a>25、如何判断数组数据类型</h2><h2 id="26、instanceOf本质"><a href="#26、instanceOf本质" class="headerlink" title="26、instanceOf本质"></a>26、instanceOf本质</h2><p>检测a的原型链（__proto__）上是否有B.prototype，若有返回true，否则false。</p>
<h2 id="27、静态成员、方法"><a href="#27、静态成员、方法" class="headerlink" title="27、静态成员、方法"></a>27、静态成员、方法</h2><p>静态变量和静态方法是挂在类自身上面的变量和方法。</p>
<p>跟类new出来的东西天生与世隔绝，仿佛是两个平行世界，不会互相影响。</p>
<p>静态的好处就是轻便，不需要new就可以直接赋值和调用，Math.PI就是静态变量，Math.random Math.floor 就是静态方法，全局范围想用就用，没什么限制。</p>
<p>类普通方法和静态方法之间的交互:</p>
<p>类的普通方法里也可以调用自身的静态函数和静态方法，比如在构造函数里搞个计数器来计算当前类被实例化过多少次。</p>
<p>在静态方法中虽然不能访问this，但是你可以把实例作为形参传进静态方法做处理，或者可以在静态方法中搞工厂函数，对某个类进行实例化后返回实例等等。单例模式就是利用静态方法实现的。</p>
<p>ES5继承的时候直接把父类身上的属性直接拷过去，方法直接引用过去，就是继承了。<br>父类有的让子类也有我认为就是实现了继承。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">PersonType</span>(<span class="params">name</span>)</span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.name = name;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//静态方法</span></span><br><span class="line">PersonType.create = <span class="function"><span class="keyword">function</span>(<span class="params">name</span>)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> PersonType(name);</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">//实例方法</span></span><br><span class="line">PersonType.prototype.sayName = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="keyword">this</span>.name)</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">var</span> person = PersonType.create(<span class="string">'zoe'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">由于PersonType.create()使用的数据不依赖PersonType的实例，其会被认为是一个静态方法。</span></span><br><span class="line"><span class="comment">ES6类语法创建了静态成员，只需要在方法或访问器属性名前使用静态注释即可</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PersonClass</span> </span>&#123;</span><br><span class="line">    <span class="comment">//等价于PersonType构造函数</span></span><br><span class="line">    <span class="keyword">constructor</span>(name)&#123;</span><br><span class="line">        <span class="keyword">this</span>.name = name</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//等价于PersonType.prototype.sayName</span></span><br><span class="line">    sayName()&#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">this</span>.name)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//等价于PersonType.create</span></span><br><span class="line">    <span class="keyword">static</span> create(name)&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> PersonClass(name);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> person = PersonClass.create(<span class="string">'zoe'</span>);</span><br><span class="line"><span class="comment">//不可在实例中访问静态成员，必须要直接在类中访问静态成员，所以是PersonClass.create()</span></span><br></pre></td></tr></table></figure>


      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/06/23/%E5%89%8D%E7%AB%AF/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E4%B8%8B%E8%BD%BD%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/06/23/%E5%89%8D%E7%AB%AF/%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E4%B8%8B%E8%BD%BD%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98/" class="post-title-link" itemprop="url">文件上传下载相关问题</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-06-23 19:36:18" itemprop="dateCreated datePublished" datetime="2021-06-23T19:36:18+08:00">2021-06-23</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:25:12" itemprop="dateModified" datetime="2021-11-03T16:25:12+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E5%89%8D%E7%AB%AF%E9%A1%B5%E9%9D%A2/" itemprop="url" rel="index"><span itemprop="name">前端页面</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>之前的项目上有实现过文件的上传、下载的相关功能；这里大概想自己总结一下吧，下载的功能设计比较简单，因为文件内容体积不大，直接利用Blob对象进行下载操作实现即可。</p>
<p>而对于文件的上传操作，利用element的upload组件封装的API进行文件选择、类型判断、多文件上传、文件预览的解决。同样由于接口那边对文件类型有要求，还要加上判断和类型输入错误的提示；上传进度条也通过upload组件的方法实现。但为了能够自定义上传效果，并没有直接用API提供的action,而是自己用axios.post()来进行异步上传。同时，由于上传的文件体积可能过大，因此需要考虑文件切片上传和断点续传的问题。</p>
<h1 id="文件下载"><a href="#文件下载" class="headerlink" title="文件下载"></a>文件下载</h1><h2 id="Blob对象"><a href="#Blob对象" class="headerlink" title="Blob对象"></a>Blob对象</h2><p>在 AJAX 请求中，指定 <code>responseType</code> 属性为 <code>blob</code> ，皆可以下下载一个 Blob 对象。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getBlob</span>(<span class="params">url, callback</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> xhr = <span class="keyword">new</span> XMLHttpRequest();</span><br><span class="line">  xhr.open(<span class="string">'GET'</span>, url);</span><br><span class="line">  xhr.responseType = <span class="string">'blob'</span>;</span><br><span class="line">  xhr.onload = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    callback(xhr.response);</span><br><span class="line">  &#125;</span><br><span class="line">  xhr.send(<span class="literal">null</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="a标签实现下载"><a href="#a标签实现下载" class="headerlink" title="a标签实现下载"></a>a标签实现下载</h2><p>同时，之后我们可以使用URL.createObjectURL()方法，针对Blob对象生成一个临时URL；在生成一个a标签，将其href属性指定下载链接、dowload属性指定文件名，再利用click事件触发下载即可。下面是简单的示例代码：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">exportDam() &#123;</span><br><span class="line">    exportdam().then(<span class="function"><span class="keyword">function</span> (<span class="params">res</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!res) &#123;</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">let</span> url = <span class="built_in">window</span>.URL.createObjectURL(res.data);</span><br><span class="line">        <span class="keyword">let</span> alink = <span class="built_in">document</span>.createElement(<span class="string">'a'</span>);</span><br><span class="line">        alink.style.display = <span class="string">'none'</span>;</span><br><span class="line">        alink.href = url;</span><br><span class="line">        alink.setAttribute(<span class="string">'download'</span>, <span class="string">'xxxxx'</span> + <span class="string">'.xxxx'</span>);</span><br><span class="line">        <span class="built_in">document</span>.body.appendChild(alink);</span><br><span class="line">        alink.click();</span><br><span class="line">        <span class="built_in">document</span>.body.removeChild(alink);</span><br><span class="line">        <span class="built_in">window</span>.URL.revokeObjectURL(alink.href);</span><br><span class="line">    &#125;).catch(<span class="function"><span class="params">error</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(error);</span><br><span class="line">    &#125;)</span><br><span class="line"><span class="comment">// this.$http.get('http://xxxxxxxx/xxxxxx/xxxx',&#123;responseType:'blob'&#125;).then(res =&gt; &#123;</span></span><br><span class="line">          <span class="comment">//</span></span><br><span class="line">          <span class="comment">// &#125;)</span></span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure>

<h1 id="文件上传"><a href="#文件上传" class="headerlink" title="文件上传"></a>文件上传</h1><h2 id="功能实现"><a href="#功能实现" class="headerlink" title="功能实现"></a>功能实现</h2><p>在实现文件上传之前，我们先要判断文件的类型与后端所要求的文件类型是否一致，常见的方法如下：</p>
<h3 id="判断文件类型"><a href="#判断文件类型" class="headerlink" title="判断文件类型"></a>判断文件类型</h3><p>1、通过后缀名来判断文件的类型：一般情况下能够起到应有的效果，但是当用户人为地修改后缀名时，则这个方法就失去了作用，这个时候，我们就需要文件头的标准编码来进行判断文件的类别了。</p>
<p>2、文件头编码判断:一个文件的文件头会保存着该文件的类型的信息，因此我们封装一个方法，将文件头转化成16进制编码格式，之后要判断一个文件是什么类型时，直接将前4个字节传递进去。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> blobToString = <span class="function">(<span class="params">blob</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> reader = <span class="keyword">new</span> FileReader();</span><br><span class="line">    reader.onload = <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> result = (reader.result)</span><br><span class="line">        .split(<span class="string">""</span>)</span><br><span class="line">				<span class="comment">// 转化成 asc 码</span></span><br><span class="line">        .map(<span class="function">(<span class="params">v</span>) =&gt;</span> v.charCodeAt(<span class="number">0</span>))</span><br><span class="line">				<span class="comment">// 转化成 16 进制</span></span><br><span class="line">        .map(<span class="function">(<span class="params">v</span>) =&gt;</span> v.toString(<span class="number">16</span>))</span><br><span class="line">				<span class="comment">// 补齐 0</span></span><br><span class="line">        .map(<span class="function">(<span class="params">v</span>) =&gt;</span> v.padStart(<span class="number">2</span>, <span class="string">"0"</span>))</span><br><span class="line">        .join(<span class="string">" "</span>);</span><br><span class="line">      resolve(result);</span><br><span class="line">    &#125;;</span><br><span class="line">    reader.readAsBinaryString(blob);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> isGif = <span class="keyword">async</span> (file) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> ret = <span class="keyword">await</span> blobToString(file.slice(<span class="number">0</span>, <span class="number">4</span>));</span><br><span class="line">  <span class="keyword">return</span> ret === <span class="string">"47 49 46 38"</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h3 id="FormData上传"><a href="#FormData上传" class="headerlink" title="FormData上传"></a>FormData上传</h3><p>在不考虑文件大小的情况下，文件上传的实现直接用FormData对象来组装键值对即可</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> files = e.target.files <span class="comment">// 获取input的file对象</span></span><br><span class="line"><span class="keyword">let</span> formData = <span class="keyword">new</span> FormData();</span><br><span class="line">formData.append(<span class="string">'file'</span>, file);</span><br><span class="line">axios.post(url, formData);</span><br></pre></td></tr></table></figure>

<h2 id="大文件上传"><a href="#大文件上传" class="headerlink" title="大文件上传"></a>大文件上传</h2><p>大文件上传最主要的问题就在于：<strong>在同一个请求中，要上传大量的数据，导致整个过程会比较漫长，且失败后需要重头开始上传</strong>。试想，如果我们将这个请求拆分成多个请求，每个请求的时间就会缩短，且如果某个请求失败，只需要重新发送这一次请求即可，无需从头开始，这样是否可以解决大文件上传的问题呢？</p>
<p>综合上面的问题，看来大文件上传需要实现下面几个需求</p>
<p>1、支持拆分上传请求(即切片)</p>
<p>2、支持断点续传</p>
<p>3、支持显示上传进度和暂停上传</p>
<p>接下来让我们依次实现这些功能，看起来最主要的功能应该就是切片了。</p>
<h3 id="文件切片上传"><a href="#文件切片上传" class="headerlink" title="文件切片上传"></a>文件切片上传</h3><p>在JavaScript中，文件FIle对象是Blob对象的子类，Blob对象包含一个重要的方法<code>slice</code>，通过这个方法，我们就可以对二进制文件进行拆分。这样将文件拆分成<code>piece</code>大小的分块，然后每次请求只需要上传这一个部分的分块即可</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  data: <span class="function"><span class="params">()</span> =&gt;</span> (&#123;</span><br><span class="line">    container: &#123;</span><br><span class="line">      file: <span class="literal">null</span></span><br><span class="line">    &#125;，</span><br><span class="line">   data: []</span><br><span class="line">  &#125;),</span><br><span class="line">  methods: &#123;</span><br><span class="line">    request() &#123;&#125;,</span><br><span class="line">    handleFileChange() &#123;&#125;,</span><br><span class="line">    <span class="comment">// 生成文件切片</span></span><br><span class="line">    createFileChunk(file, size = SIZE) &#123;</span><br><span class="line">     <span class="keyword">const</span> fileChunkList = [];</span><br><span class="line">      <span class="keyword">let</span> cur = <span class="number">0</span>;</span><br><span class="line">      <span class="keyword">while</span> (cur &lt; file.size) &#123;</span><br><span class="line">        fileChunkList.push(&#123; <span class="attr">file</span>: file.slice(cur, cur + size) &#125;);</span><br><span class="line">        cur += size;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> fileChunkList;</span><br><span class="line">    &#125;,</span><br><span class="line">   <span class="comment">// 上传切片</span></span><br><span class="line">    <span class="keyword">async</span> uploadChunks() &#123;</span><br><span class="line">      <span class="keyword">const</span> requestList = <span class="keyword">this</span>.data</span><br><span class="line">       .map(<span class="function">(<span class="params">&#123; chunk，hash &#125;</span>) =&gt;</span> &#123;</span><br><span class="line">         <span class="keyword">const</span> formData = <span class="keyword">new</span> FormData();</span><br><span class="line">          formData.append(<span class="string">"chunk"</span>, chunk);</span><br><span class="line">          formData.append(<span class="string">"hash"</span>, hash);</span><br><span class="line">          formData.append(<span class="string">"filename"</span>, <span class="keyword">this</span>.container.file.name);</span><br><span class="line">          <span class="keyword">return</span> &#123; formData &#125;;</span><br><span class="line">        &#125;)</span><br><span class="line">        .map(<span class="keyword">async</span> (&#123; formData &#125;) =&gt;</span><br><span class="line">          <span class="keyword">this</span>.request(&#123;</span><br><span class="line">            url: <span class="string">"http://localhost:3000"</span>,</span><br><span class="line">            data: formData</span><br><span class="line">          &#125;)</span><br><span class="line">        );</span><br><span class="line">      <span class="keyword">await</span> <span class="built_in">Promise</span>.all(requestList); <span class="comment">// 并发切片</span></span><br><span class="line">      <span class="keyword">await</span> <span class="keyword">this</span>.mergeRequest();</span><br><span class="line">    &#125;,</span><br><span class="line">     <span class="keyword">async</span> mergeRequest() &#123;</span><br><span class="line">      <span class="keyword">await</span> <span class="keyword">this</span>.request(&#123;</span><br><span class="line">        url: <span class="string">"http://localhost:3000/merge"</span>,</span><br><span class="line">        headers: &#123;</span><br><span class="line">          <span class="string">"content-type"</span>: <span class="string">"application/json"</span></span><br><span class="line">        &#125;,</span><br><span class="line">        data: <span class="built_in">JSON</span>.stringify(&#123;</span><br><span class="line">          filename: <span class="keyword">this</span>.container.file.name</span><br><span class="line">        &#125;)</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="keyword">async</span> handleUpload() &#123;</span><br><span class="line">      <span class="keyword">if</span> (!<span class="keyword">this</span>.container.file) <span class="keyword">return</span>;</span><br><span class="line">      <span class="keyword">const</span> fileChunkList = <span class="keyword">this</span>.createFileChunk(<span class="keyword">this</span>.container.file);</span><br><span class="line">      <span class="keyword">this</span>.data = fileChunkList.map(<span class="function">(<span class="params">&#123; file &#125;，index</span>) =&gt;</span> (&#123;</span><br><span class="line">        chunk: file,</span><br><span class="line">        hash: <span class="keyword">this</span>.container.file.name + <span class="string">"-"</span> + index <span class="comment">// 文件名 + 数组下标</span></span><br><span class="line">      &#125;));</span><br><span class="line">      <span class="keyword">await</span> <span class="keyword">this</span>.uploadChunks();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>整个的逻辑其实并不复杂：1、当点击上传按钮时，调用 <code>createFileChunk</code> 将文件切片，切片数量通过文件大小控制，这里设置 10MB，也就是说 100 MB 的文件会被分成 10 个切片。</p>
<p>2、createFileChunk 内使用 while 循环和 slice 方法将切片放入 <code>fileChunkList</code> 数组中返回</p>
<p>3、在生成文件切片时，需要给每个切片一个标识作为 hash，这里暂时使用<code>文件名 + 下标</code>，这样后端可以知道当前切片是第几个切片，用于之后的合并切片</p>
<p>4、随后调用 <code>uploadChunks</code> 上传所有的文件切片，将文件切片，切片 hash，以及文件名放入 FormData 中，再调用上一步的 <code>request</code> 函数返回一个 proimise，最后调用 Promise.all 并发上传所有的切片。</p>
<p>5、在上传完所有的切片后，需要再向服务器发送一个合并请求，即通知服务器切片已经上传完了，让服务器开始文件的合并的流程。</p>
<p>这样我们就实现了前端方面的文件切片传输的过程，并在最后传输完成之后，向服务端发送了一个文件合并的请求，告诉服务端文件已经传输完毕。</p>
<h3 id="文件拼接"><a href="#文件拼接" class="headerlink" title="文件拼接"></a>文件拼接</h3><p>用node的http模块构建服务器后，我们使用 <code>multiparty</code> 包处理前端传来的 FormData。在 multiparty.parse 的回调中，files 参数保存了 FormData 中文件，fields 参数保存了 FormData 中非文件的字段。</p>
<p>查看 multiparty 处理后的 chunk 对象，path 是存储临时文件的路径，size 是临时文件大小，在 multiparty 文档中提到可以使用 fs.rename移动临时文件，即移动文件切片。在接受文件切片时，需要先创建存储切片的文件夹，由于前端在发送每个切片时额外携带了唯一值 hash，所以以 hash 作为文件名，将切片从临时路径移动切片文件夹中。在接收到前端发送的合并请求后，服务端将文件夹下的所有切片进行合并</p>
<p>由于前端在发送合并请求时会携带文件名，服务端根据文件名可以找到上一步创建的切片文件夹。接着使用 fs.createWriteStream 创建一个可写流，可写流文件名就是<strong>切片文件夹名 + 后缀名</strong>组合而成。随后遍历整个切片文件夹，将切片通过 fs.createReadStream 创建可读流，传输合并到目标文件中。值得注意的是每次可读流都会传输到可写流的指定位置，这是通过 createWriteStream 的第二个参数 start/end 控制的，目的是能够并发合并多个可读流到可写流中，这样即使流的顺序不同也能传输到正确的位置，所以这里还需要让前端在请求的时候多提供一个 size 参数。</p>
<p>其实也可以等上一个切片合并完后再合并下个切片，这样就不需要指定位置，但传输速度会降低，所以使用了并发合并的手段，接着只要保证每次合并完成后删除这个切片，等所有切片都合并完毕后最后删除切片文件夹即可</p>
<h3 id="上传进度条"><a href="#上传进度条" class="headerlink" title="上传进度条"></a>上传进度条</h3><p>上传进度分两种，一个是每个切片的上传进度，另一个是整个文件的上传进度，而整个文件的上传进度是基于每个切片上传进度计算而来，所以我们先实现切片的上传进度</p>
<p>切片进度条：XMLHttpRequest 原生支持上传进度的监听，只需要监听 upload.onprogress 即可，我们在原来的 request 基础上传入 onProgress 参数，给 XMLHttpRequest 注册监听事件</p>
<p>由于每个切片都需要触发独立的监听事件，所以还需要一个工厂函数，根据传入的切片返回不同的监听函数。并在原先的前端上传逻辑中新增监听函数部分。每个切片在上传时都会通过监听函数更新 data 数组对应元素的 percentage 属性，之后把将 data 数组放到视图中展示即可</p>
<p>文件进度条：将每个切片已上传的部分累加，除以整个文件的大小，就能得出当前文件的上传进度，所以这里使用 Vue 计算属性。</p>
<h2 id="断点续传功能"><a href="#断点续传功能" class="headerlink" title="断点续传功能"></a>断点续传功能</h2><p>断点续传的原理在于前端/服务端需要<code>记住</code>已上传的切片，这样下次上传就可以跳过之前已上传的部分，有两种方案实现记忆的功能：</p>
<p>1、前端使用 localStorage 记录已上传的切片 hash；</p>
<p>2、服务端保存已上传的切片 hash，前端每次上传前向服务端获取已上传的切片；</p>
<p>第一种是前端的解决方案，第二种是服务端，而前端方案有一个缺陷，如果换了个浏览器就失去了记忆的效果，所以这里选取后者</p>
<h3 id="生成hash"><a href="#生成hash" class="headerlink" title="生成hash"></a>生成hash</h3><p>无论是前端还是服务端，都必须要生成文件和切片的 hash，<code>之前我们使用文件名 + 切片下标作为切片 hash</code>，这样做文件名一旦修改就失去了效果，而事实上只要文件内容不变，hash 就不应该变化，所以正确的做法是<code>根据文件内容生成 hash</code>，所以我们修改一下 hash 的生成规则</p>
<p>这里用到另一个库 <code>spark-md5</code>，它可以根据文件内容计算出文件的 hash 值，另外考虑到如果上传一个超大文件，读取文件内容计算 hash 是非常耗费时间的，并且会<code>引起 UI 的阻塞</code>，导致页面假死状态，所以我们使用 web-worker 在 worker 线程计算 hash，这样用户仍可以在主界面正常的交互</p>
<p>由于实例化 web-worker 时，参数是一个 js 文件路径且不能跨域，所以我们单独创建一个 hash.js 文件放在 public 目录下，另外在 worker 中也是不允许访问 dom 的，但它提供了<code>importScripts</code> 函数用于导入外部脚本，通过它导入 spark-md5。</p>
<p>在 worker 线程中，接受文件切片 fileChunkList，利用 FileReader 读取每个切片的 ArrayBuffer 并不断传入 spark-md5 中，每计算完一个切片通过 postMessage 向主线程发送一个进度事件，全部完成后将最终的 hash 发送给主线程。spark-md5 需要根据所有切片才能算出一个 hash 值，不能直接将整个文件放入计算，否则即使不同文件也会有相同的 hash，具体可以看官方文档</p>
<p>spark-md5 需要根据所有切片才能算出一个 hash 值，不能直接将整个文件放入计算，否则即使不同文件也会有相同的 hash，具体可以看官方文档</p>
<p>主线程使用 <code>postMessage</code> 给 worker 线程传入所有切片 fileChunkList，并监听 worker 线程发出的 postMessage 事件拿到文件 hash</p>
<p>服务端则使用 hash 作为切片文件夹名，hash + 下标作为切片名，hash + 扩展名作为文件名，没有新增的逻辑</p>
<h3 id="文件秒传"><a href="#文件秒传" class="headerlink" title="文件秒传"></a>文件秒传</h3><p>在实现断点续传前先简单介绍一下文件秒传：所谓的文件秒传，即在服务端已经存在了上传的资源，所以当用户<code>再次上传</code>时会直接提示上传成功。</p>
<p>文件秒传需要依赖上一步生成的 hash，即在<code>上传前</code>，先计算出文件 hash，并把 hash 发送给服务端进行验证，由于 hash 的唯一性，所以一旦服务端能找到 hash 相同的文件，则直接返回上传成功的信息即可。</p>
<h3 id="暂停上传"><a href="#暂停上传" class="headerlink" title="暂停上传"></a>暂停上传</h3><p>讲完了生成 hash 和文件秒传，回到断点续传。断点续传顾名思义即断点 + 续传，所以我们第一步先实现“断点”，也就是暂停上传。原理是使用 XMLHttpRequest 的 <code>abort</code> 方法，可以取消一个 xhr 请求的发送，为此我们需要将上传每个切片的 xhr 对象保存起来，我们再改造一下 request 方法。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">request(&#123;</span><br><span class="line">   url,</span><br><span class="line">   method = <span class="string">"post"</span>,</span><br><span class="line">   data,</span><br><span class="line">   headers = &#123;&#125;,</span><br><span class="line">   onProgress = <span class="function"><span class="params">e</span> =&gt;</span> e,</span><br><span class="line">   requestList</span><br><span class="line"> &#125;) &#123;</span><br><span class="line">   <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="params">resolve</span> =&gt;</span> &#123;</span><br><span class="line">     <span class="keyword">const</span> xhr = <span class="keyword">new</span> XMLHttpRequest();</span><br><span class="line">     xhr.upload.onprogress = onProgress;</span><br><span class="line">     xhr.open(method, url);</span><br><span class="line">     <span class="built_in">Object</span>.keys(headers).forEach(<span class="function"><span class="params">key</span> =&gt;</span></span><br><span class="line">       xhr.setRequestHeader(key, headers[key])</span><br><span class="line">     );</span><br><span class="line">     xhr.send(data);</span><br><span class="line">     xhr.onload = <span class="function"><span class="params">e</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// 将请求成功的 xhr 从列表中删除</span></span><br><span class="line">        <span class="keyword">if</span> (requestList) &#123;</span><br><span class="line">          <span class="keyword">const</span> xhrIndex = requestList.findIndex(<span class="function"><span class="params">item</span> =&gt;</span> item === xhr);</span><br><span class="line">          requestList.splice(xhrIndex, <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">       resolve(&#123;</span><br><span class="line">         data: e.target.response</span><br><span class="line">       &#125;);</span><br><span class="line">     &#125;;</span><br><span class="line">      <span class="comment">// 暴露当前 xhr 给外部</span></span><br><span class="line">      requestList?.push(xhr);</span><br><span class="line">   &#125;);</span><br><span class="line"> &#125;,</span><br></pre></td></tr></table></figure>

<p>这样在上传切片时传入 requestList 数组作为参数，request 方法就会将所有的 xhr 保存在数组中了。每当一个切片上传成功时，将对应的 xhr 从 requestList 中删除，所以 requestList 中只保存<code>正在上传切片的 xhr</code>。之后新建一个暂停按钮，当点击按钮时，调用保存在 requestList 中 xhr 的 abort 方法，即取消并清空所有正在上传的切片。点击暂停按钮可以看到 xhr 都被取消了。</p>
<h3 id="恢复上传"><a href="#恢复上传" class="headerlink" title="恢复上传"></a>恢复上传</h3><p>之前在介绍断点续传的时提到使用第二种服务端存储的方式实现续传</p>
<p>由于当文件切片上传后，服务端会建立一个文件夹存储所有上传的切片，所以每次前端上传前可以调用一个接口，服务端将已上传的切片的切片名返回，前端再跳过这些已经上传切片，这样就实现了“续传”的效果</p>
<p>而这个接口可以和之前秒传的验证接口合并，前端每次上传前发送一个验证的请求，返回两种结果</p>
<p>1、服务端已存在该文件，不需要再次上传</p>
<p>2、服务端不存在该文件或者已上传部分文件切片，通知前端进行上传，并把<strong>已上传</strong>的文件切片返回给前端。</p>
<p>所以我们改造一下之前文件秒传的服务端验证接口。接着回到前端，前端有两个地方需要调用验证的接口</p>
<p>1、点击上传时，检查是否需要上传和已上传的切片</p>
<p>2、点击暂停后的恢复上传，返回已上传的切片</p>
<p>新增恢复按钮并改造原来上传切片的逻辑；这里给原来上传切片的函数新增 uploadedList 参数，即上图中服务端返回的切片名列表，通过 filter 过滤掉已上传的切片，并且由于新增了已上传的部分，所以之前合并接口的触发条件做了一些改动。到这里断点续传的功能基本完成了。</p>
<h3 id="进度条修改"><a href="#进度条修改" class="headerlink" title="进度条修改"></a>进度条修改</h3><p>虽然实现了断点续传，但还需要修改一下进度条的显示规则，否则在暂停上传/接收到已上传切片时的进度条会出现偏差：</p>
<p>1、切片进度条：由于在点击上传/恢复上传时，会调用验证接口返回已上传的切片，所以需要将已上传切片的进度变成 100%。uploadedList 会返回已上传的切片，在遍历所有切片时判断当前切片是否在已上传列表里即可</p>
<p>2、文件进度条：</p>
<p>之前说到文件进度条是一个计算属性，根据所有切片的上传进度计算而来，这就遇到了一个问题：点击暂停会取消并清空切片的 xhr 请求，此时如果已经上传了一部分，就会发现文件进度条有<code>倒退</code>的现象。当点击恢复时，由于重新创建了 xhr 导致切片进度清零，所以总进度条就会倒退</p>
<p>解决方案是创建一个“假”的进度条，这个假进度条基于文件进度条，但只会停止和增加，然后给用户展示这个假的进度条。</p>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>大文件上传</p>
<p>1、前端上传大文件时使用 Blob.prototype.slice 将文件切片，并发上传多个切片，最后发送一个合并的请求通知服务端合并切片</p>
<p>2、服务端接收切片并存储，收到合并请求后使用流将切片合并到最终文件</p>
<p>3、原生 XMLHttpRequest 的 upload.onprogress 对切片上传进度的监听</p>
<p>4、使用 Vue 计算属性根据每个切片的进度算出整个文件的上传进度</p>
<p>断点续传</p>
<p>1、使用 spark-md5 根据文件内容算出文件 hash</p>
<p>2、通过 hash 可以判断服务端是否已经上传该文件，从而直接提示用户上传成功（秒传）</p>
<p>3、通过 XMLHttpRequest 的 abort 方法暂停切片的上传</p>
<p>4、上传前服务端返回已经上传的切片名，前端跳过这些切片的上传</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/06/10/%E5%AD%A6%E6%A0%A1%E9%A1%B9%E7%9B%AE%E5%BA%94%E7%94%A8/leaflet.js/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/06/10/%E5%AD%A6%E6%A0%A1%E9%A1%B9%E7%9B%AE%E5%BA%94%E7%94%A8/leaflet.js/" class="post-title-link" itemprop="url">leaflet.js</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-06-10 21:28:16" itemprop="dateCreated datePublished" datetime="2021-06-10T21:28:16+08:00">2021-06-10</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:32:50" itemprop="dateModified" datetime="2021-11-03T16:32:50+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E5%AD%A6%E6%A0%A1%E9%A1%B9%E7%9B%AE%E5%BA%94%E7%94%A8/" itemprop="url" rel="index"><span itemprop="name">学校项目应用</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近因为项目原因又要重新开始了一个新坑，就是leaflet.js来实现地图模块的相关功能，虽然之前的项目里面也用过leaflet.js，而且用其内置的API实现了地图数据差值分析的功能，但当时并非我主要负责该模块，因此对其了解确实不深，不过之前在实现需求时用过百度地图和echarts的API，想来其实用法上没有太大区别吧，又不用去看源码，了解其运行本质。本篇文章主要用于记录在使用leaflet.js的过程中踩的坑与实现的简单需求吧，做一下记录，避免后续遇到类似需求时还要看原来的旧代码。</p>
<h1 id="简单实现"><a href="#简单实现" class="headerlink" title="简单实现"></a>简单实现</h1><h2 id="新建地图"><a href="#新建地图" class="headerlink" title="新建地图"></a>新建地图</h2><p>在leaflet的使用上，我使用的是supermap的外接实现，由于leaflet.js没有中文文档，而提供了大量实例的supermap无疑会让使用leaflet更加轻松。其官网地址如下：<a href="https://iclient.supermap.io/web/introduction/leafletDevelop.html#Ready%E3%80%82%E9%99%A4%E4%BA%86leaflet%E4%BB%A5%E5%A4%96%EF%BC%8Csupermap%E8%BF%98%E6%94%AF%E6%8C%81%E5%85%B6%E4%BB%96%E5%BE%88%E5%A4%9A%E5%85%B6%E4%BB%96%E5%9C%B0%E5%9B%BE%E6%88%96%E8%80%85webgl%E5%BA%93%E3%80%82" target="_blank" rel="noopener">https://iclient.supermap.io/web/introduction/leafletDevelop.html#Ready。除了leaflet以外，supermap还支持其他很多其他地图或者webgl库。</a></p>
<p>下面是一个基本的天地图构建，其中的key需要在天地图官网申请，这里我设置了缩放，和setView设置了初始的焦点。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">addmap() &#123;</span><br><span class="line">      <span class="keyword">let</span> tianditu = L.supermap.tiandituTileLayer(&#123;</span><br><span class="line">        <span class="comment">// key: "d9961c5cd1d22dfd80f3984e44cd2478",</span></span><br><span class="line">        key: <span class="string">"3352a450f393157074884346f937c47d"</span>,</span><br><span class="line">        <span class="comment">// key: "e455500d170d9a5b5b3bf85a5b323dbf",</span></span><br><span class="line">        layerType: <span class="string">"img"</span>,</span><br><span class="line">        noWrap: <span class="literal">false</span>,</span><br><span class="line">      &#125;)</span><br><span class="line">      <span class="comment">// 初始化地图信息</span></span><br><span class="line">      <span class="keyword">this</span>.map = L.map(<span class="string">'map'</span>, &#123;</span><br><span class="line">        center: [<span class="number">0</span>,<span class="number">0</span>],</span><br><span class="line">        zoomControl:<span class="literal">false</span>,</span><br><span class="line">        crs: L.CRS.TianDiTu_WGS84,</span><br><span class="line">        dragging: <span class="literal">true</span>,</span><br><span class="line">        doubleClickZoom: <span class="literal">false</span>,</span><br><span class="line">        <span class="comment">// layers: [tianditu, rivers, sections,basins, dams],</span></span><br><span class="line">        layers: [tianditu],</span><br><span class="line">        attributionControl: <span class="literal">false</span>,</span><br><span class="line">        zoom: <span class="number">8</span></span><br><span class="line">      &#125;).setView([<span class="number">30.7288</span>, <span class="number">112.33226</span>], <span class="number">7</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//这个label是用于标注出地图上全部的行政区划相关内容</span></span><br><span class="line">      <span class="keyword">let</span> tiandituLabel = L.supermap.tiandituTileLayer(&#123;</span><br><span class="line">        isLabel: <span class="literal">true</span>,</span><br><span class="line">        <span class="comment">// key: "d9961c5cd1d22dfd80f3984e44cd2478"</span></span><br><span class="line">        key: <span class="string">"3352a450f393157074884346f937c47d"</span></span><br><span class="line">        <span class="comment">// key: "e455500d170d9a5b5b3bf85a5b323dbf"</span></span><br><span class="line">      &#125;).addTo(<span class="keyword">this</span>.map);</span><br><span class="line"></span><br><span class="line">      L.control.zoom(&#123;</span><br><span class="line">        zoomInTitle:<span class="string">"放大"</span>,</span><br><span class="line">        zoomOutTitle:<span class="string">'缩小'</span>,</span><br><span class="line">      &#125;).addTo(<span class="keyword">this</span>.map);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="边界绘制与遮罩生成"><a href="#边界绘制与遮罩生成" class="headerlink" title="边界绘制与遮罩生成"></a>边界绘制与遮罩生成</h2><p>需求要求绘制出湖北省的边界，且分割出其不同市的行政区划，这里我用dataV官网上获取的湖北省地址的json数据，地址如下：<a href="http://datav.aliyun.com/tools/atlas/index.html%E3%80%82%E5%9C%A8leaflet%E4%B8%AD%E6%B7%BB%E5%8A%A0%E8%BE%B9%E7%95%8C%E7%9A%84%E4%BB%A3%E7%A0%81%E5%A6%82%E4%B8%8B%EF%BC%9A" target="_blank" rel="noopener">http://datav.aliyun.com/tools/atlas/index.html。在leaflet中添加边界的代码如下：</a></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//在地图上添加湖北省边界</span></span><br><span class="line">      <span class="keyword">let</span> style = &#123;</span><br><span class="line">        <span class="string">"color"</span>: <span class="string">"#0066FF"</span>, <span class="comment">//边框颜色</span></span><br><span class="line">        <span class="string">"weight"</span>: <span class="number">1</span>, <span class="comment">//边框粗细</span></span><br><span class="line">        <span class="string">"opacity"</span>: <span class="number">0.7</span>, <span class="comment">//透明度</span></span><br><span class="line">        <span class="string">"fillColor"</span>: <span class="string">'red'</span>, <span class="comment">//区域填充颜色</span></span><br><span class="line">        <span class="string">"fillOpacity"</span>: <span class="number">0</span>, <span class="comment">//区域填充颜色的透明</span></span><br><span class="line">      &#125;;</span><br><span class="line"></span><br><span class="line">      L.geoJSON(features,&#123;<span class="attr">style</span>:style&#125;).addTo(<span class="keyword">this</span>.map);</span><br><span class="line"><span class="comment">//这里传入的参数features是湖北省的经纬度json格式数据</span></span><br></pre></td></tr></table></figure>

<p>之后，需求要求对除湖北省外的其他地区做遮罩处理，这里的处理方式比较麻烦，但其实已经有了完整的解决方案，因此也没有花太多时间去解决。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//在drawmap()中调用，绘制遮罩</span></span><br><span class="line"><span class="keyword">this</span>.drawShadowBound(simpleHubei.features[<span class="number">0</span>].geometry.coordinates);</span><br><span class="line"></span><br><span class="line">drawShadowBound(coordinates,boundData)&#123;</span><br><span class="line">      <span class="comment">/*画遮蔽层的相关方法</span></span><br><span class="line"><span class="comment">     *思路: 首先在中国地图最外画一圈，圈住理论上所有的中国领土，然后再将每个闭合区域合并进来，并全部连到西北角。</span></span><br><span class="line"><span class="comment">     *      这样就做出了一个经过多次西北角的闭合多边形*/</span></span><br><span class="line">      <span class="comment">//定义中国东南西北端点，作为第一层</span></span><br><span class="line">      <span class="keyword">let</span> pNW = &#123;<span class="attr">lat</span>: <span class="number">59.0</span>, <span class="attr">lng</span>: <span class="number">73.0</span>&#125;;</span><br><span class="line">      <span class="keyword">let</span> pNE = &#123;<span class="attr">lat</span>: <span class="number">59.0</span>, <span class="attr">lng</span>: <span class="number">136.0</span>&#125;;</span><br><span class="line">      <span class="keyword">let</span> pSE = &#123;<span class="attr">lat</span>: <span class="number">3.0</span>, <span class="attr">lng</span>: <span class="number">136.0</span>&#125;;</span><br><span class="line">      <span class="keyword">let</span> pSW = &#123;<span class="attr">lat</span>: <span class="number">3.0</span>, <span class="attr">lng</span>: <span class="number">73.0</span>&#125;;</span><br><span class="line">      <span class="comment">// let pNW = &#123;lat: 90, lng:-180&#125;;</span></span><br><span class="line">      <span class="comment">// let pNE = &#123;lat: 90, lng:180 &#125;;</span></span><br><span class="line">      <span class="comment">// let pSE = &#123;lat: -90, lng: -180&#125;;</span></span><br><span class="line">      <span class="comment">// let pSW = &#123;lat:90, lng:180&#125;;</span></span><br><span class="line">      <span class="comment">//向数组中添加一次闭合多边形，并将西北角再加一次作为之后画闭合区域的起点</span></span><br><span class="line">      <span class="keyword">let</span> pArray = [];</span><br><span class="line">      pArray.push(pNW);</span><br><span class="line">      pArray.push(pSW);</span><br><span class="line">      pArray.push(pSE);</span><br><span class="line">      pArray.push(pNE);</span><br><span class="line">      pArray.push(pNW);</span><br><span class="line">      <span class="comment">//循环添加各闭合区域</span></span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; coordinates.length; i++) &#123;</span><br><span class="line">        <span class="keyword">let</span> points = [];</span><br><span class="line">        <span class="keyword">let</span> item1 = coordinates[i]</span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">let</span> j = <span class="number">0</span>, l1=item1.length; j&lt;l1; j++)&#123;</span><br><span class="line">          <span class="keyword">let</span> item2 = item1[j]</span><br><span class="line">          <span class="keyword">for</span>(<span class="keyword">let</span> k = <span class="number">0</span>, l2 = item2.length; k&lt;l2; k++)&#123;</span><br><span class="line">            <span class="keyword">let</span> item3 = item2[k]</span><br><span class="line">            points.push(&#123;<span class="attr">lat</span>: item3[<span class="number">1</span>], <span class="attr">lng</span>: item3[<span class="number">0</span>]&#125;);</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//将闭合区域加到遮蔽层上，每次添加完后要再加一次西北角作为下次添加的起点和最后一次的终点</span></span><br><span class="line">        pArray = pArray.concat(points);</span><br><span class="line">        pArray.push(pArray[<span class="number">0</span>]);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="comment">//添加遮蔽层</span></span><br><span class="line">      <span class="keyword">let</span> plyall = L.polygon(pArray, &#123;<span class="attr">color</span>: <span class="string">'transparent'</span>, <span class="attr">fillColor</span>: <span class="string">'#282525'</span>, <span class="attr">fillOpacity</span>: <span class="number">0.8</span>&#125;); </span><br><span class="line">    <span class="comment">//建立多边形覆盖物</span></span><br><span class="line">      <span class="comment">//let plyall = L.polygon(pArray, &#123;color: '#282525', fillColor: 'transparent', fillOpacity: 0.5&#125;); </span></span><br><span class="line">    <span class="comment">//建立多边形覆盖物</span></span><br><span class="line">      plyall.addTo(<span class="keyword">this</span>.map);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>


      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/05/23/%E5%89%8D%E7%AB%AF/vue%E4%B8%8Ejs/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/05/23/%E5%89%8D%E7%AB%AF/vue%E4%B8%8Ejs/" class="post-title-link" itemprop="url">AJAX与Js</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-05-23 19:36:18" itemprop="dateCreated datePublished" datetime="2021-05-23T19:36:18+08:00">2021-05-23</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:26:46" itemprop="dateModified" datetime="2021-11-03T16:26:46+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E5%89%8D%E7%AB%AF%E9%A1%B5%E9%9D%A2/" itemprop="url" rel="index"><span itemprop="name">前端页面</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h2 id="vue"><a href="#vue" class="headerlink" title="vue"></a>vue</h2><h3 id="art-template"><a href="#art-template" class="headerlink" title="art-template"></a>art-template</h3><p><img src="/../../image/vue%E4%B8%8Ejs/art-template.png" alt="art-template"></p>
<h3 id="render函数"><a href="#render函数" class="headerlink" title="render函数"></a>render函数</h3><p><img src="/../../image/vue%E4%B8%8Ejs/Render%E5%87%BD%E6%95%B0.png" alt="Render函数"></p>
<h3 id="v-model"><a href="#v-model" class="headerlink" title="v-model"></a>v-model</h3><p><img src="/../../image/vue%E4%B8%8Ejs/v-model.png" alt="v-model"></p>
<h3 id="MVVM"><a href="#MVVM" class="headerlink" title="MVVM"></a>MVVM</h3><p><img src="/../../image/vue%E4%B8%8Ejs/Vue222.png" alt="Vue222"></p>
<h3 id="Vuex"><a href="#Vuex" class="headerlink" title="Vuex"></a>Vuex</h3><p><img src="/../../image/vue%E4%B8%8Ejs/Vuex.png" alt="Vuex"></p>
<h3 id="webpack原理"><a href="#webpack原理" class="headerlink" title="webpack原理"></a>webpack原理</h3><p><img src="/../../image/vue%E4%B8%8Ejs/webpack%E5%8E%9F%E7%90%86.png" alt="webpack原理"></p>
<h3 id="常用组件通信"><a href="#常用组件通信" class="headerlink" title="常用组件通信"></a>常用组件通信</h3><p><img src="/../../image/vue%E4%B8%8Ejs/%E5%B8%B8%E7%94%A8%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1.png" alt="常用组件通信"></p>
<h3 id="多组件通信"><a href="#多组件通信" class="headerlink" title="多组件通信"></a>多组件通信</h3><p><img src="/../../image/vue%E4%B8%8Ejs/%E5%A4%9A%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1.png" alt="多组件通信"></p>
<h3 id="获取DOM"><a href="#获取DOM" class="headerlink" title="获取DOM"></a>获取DOM</h3><p><img src="/../../image/vue%E4%B8%8Ejs/%E8%8E%B7%E5%8F%96DOM.png" alt="获取DOM"></p>
<h3 id="计算属性"><a href="#计算属性" class="headerlink" title="计算属性"></a>计算属性</h3><p><img src="/../../image/vue%E4%B8%8Ejs/%E8%AE%A1%E7%AE%97%E5%B1%9E%E6%80%A7.png" alt="计算属性"></p>
<h3 id="局部组件"><a href="#局部组件" class="headerlink" title="局部组件"></a>局部组件</h3><p><img src="/../../image/vue%E4%B8%8Ejs/%E5%B1%80%E9%83%A8%E7%BB%84%E4%BB%B6.png" alt="局部组件"></p>
<h3 id="中央事件总线"><a href="#中央事件总线" class="headerlink" title="中央事件总线"></a>中央事件总线</h3><p><img src="/../../image/vue%E4%B8%8Ejs/%E4%B8%AD%E5%A4%AE%E4%BA%8B%E4%BB%B6%E6%80%BB%E7%BA%BF.png" alt="中央事件总线"></p>
<h3 id="组件"><a href="#组件" class="headerlink" title="组件"></a>组件</h3><p><img src="/../../image/vue%E4%B8%8Ejs/%E7%BB%84%E4%BB%B6.png" alt="组件"></p>
<h3 id="组件间数据通信"><a href="#组件间数据通信" class="headerlink" title="组件间数据通信"></a>组件间数据通信</h3><p><img src="/../../image/vue%E4%B8%8Ejs/%E7%BB%84%E4%BB%B6%E9%97%B4%E6%95%B0%E6%8D%AE%E9%80%9A%E4%BF%A1.png" alt="组件间数据通信"></p>
<h3 id="组件通信"><a href="#组件通信" class="headerlink" title="组件通信"></a>组件通信</h3><p><img src="/../../image/vue%E4%B8%8Ejs/%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1%E5%9B%9B.png" alt="组件通信四"></p>
<h2 id="js"><a href="#js" class="headerlink" title="js"></a>js</h2><h3 id="内存模型"><a href="#内存模型" class="headerlink" title="内存模型"></a>内存模型</h3><p><img src="/../../image/vue%E4%B8%8Ejs/1760011-20200512143232426-796587478.png" alt="1760011-20200512143232426-796587478"></p>
<h3 id="任务队列"><a href="#任务队列" class="headerlink" title="任务队列"></a>任务队列</h3><p><img src="/../../image/vue%E4%B8%8Ejs/1760011-20200512143348561-1197833870.png" alt="1760011-20200512143348561-1197833870"></p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/05/19/node/koa%E6%BA%90%E7%A0%81/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/05/19/node/koa%E6%BA%90%E7%A0%81/" class="post-title-link" itemprop="url">KOA源码</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-05-19 12:18:39" itemprop="dateCreated datePublished" datetime="2021-05-19T12:18:39+08:00">2021-05-19</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:20:03" itemprop="dateModified" datetime="2021-11-03T16:20:03+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/node/" itemprop="url" rel="index"><span itemprop="name">node</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近在学习node，有用pug模板引擎来实现简单网站跟后端的搭建，搭建过程中使用到了KOA框架；KOA框架本身就比较简洁易用，相较于express是一个更加轻量级的框架，所有的功能都通过插件来实现。而且本身的源码也十分简洁，只有4个JS文件，因此就想以新手的角度分析并学习一下KOA框架源码与设计理念。</p>
<h1 id="KOA用法"><a href="#KOA用法" class="headerlink" title="KOA用法"></a>KOA用法</h1><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Koa();</span><br><span class="line"></span><br><span class="line">app.use(<span class="function">(<span class="params">ctx, next</span>) =&gt;</span> &#123;</span><br><span class="line">  ctx.body = <span class="string">'Hello World'</span>;</span><br><span class="line">&#125;);</span><br><span class="line">app.listen(<span class="number">3000</span>); </span><br><span class="line"></span><br><span class="line"><span class="comment">//下面是不用框架时的用法</span></span><br><span class="line"><span class="keyword">let</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>)</span><br><span class="line"><span class="keyword">let</span> server = http.createServer(<span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">  res.end(<span class="string">'hello world'</span>)</span><br><span class="line">&#125;)</span><br><span class="line">server.listen(<span class="number">4000</span>)</span><br></pre></td></tr></table></figure>

<p>对比发现，相对原生，koa多了两个实例上的use、listen方法，和use回调中的ctx、next两个参数。这四个不同，几乎就是koa的全部了，也是这四个不同让koa如此强大。</p>
<h2 id="listen"><a href="#listen" class="headerlink" title="listen"></a>listen</h2><p>本质上是http的语法糖，实际上还是用了http.createServer()，然后监听了一个端口。</p>
<h2 id="ctx"><a href="#ctx" class="headerlink" title="ctx"></a>ctx</h2><p>利用 <strong>上下文(context)</strong> 机制，将原来的req,res对象合二为一，并进行了大量拓展,使开发者可以方便的使用更多属性和方法，大大减少了处理字符串、提取信息的时间，免去了许多引入第三方包的过程。(例如ctx.query、ctx.path等)</p>
<h2 id="use"><a href="#use" class="headerlink" title="use"></a>use</h2><p>koa的核心 —— <strong>中间件（middleware）</strong>机制。解决了异步编程中回调地狱的问题，基于<a href="https://juejin.im/post/6844903583234834445" target="_blank" rel="noopener">Promise</a>，利用 <strong>洋葱模型</strong> 思想，使嵌套的、纠缠不清的代码变得清晰、明确，并且可拓展，可定制，借助许多第三方中间件，可以使精简的koa更加全能（例如koa-router，实现了路由）。其原理主要是一个极其精妙的 <strong>compose</strong> 函数。在使用时，用 <strong>next()</strong> 方法，从上一个中间件跳到下一个中间件。</p>
<h2 id="实现思路"><a href="#实现思路" class="headerlink" title="实现思路"></a>实现思路</h2><p>application.js为入口文件；context.js是上下文对象相关；request.js是请求对象相关；response.js是响应对象相关。</p>
<p>整体的原理无非就是use的时候拿到一个回调函数，listen的时候执行这个函数。此外，use回调函数的参数ctx拓展了很多功能，这个ctx其实就是原生的req、res经过一系列处理产生的。同样，use的时候可以拿到多个回调函数，用户使用第二个参数next()跳到下一个，并把多个use的回调函数按照规则顺序执行。</p>
<p>难点只有两个：一个是如何将原生req和res加工成ctx，另一个是如何实现中间件。</p>
<p>1、ctx其实就是一个上下文对象，request和response两个文件用来拓展属性，context文件实现代理。</p>
<p>2、源码中的中间件由一个中间件执行模块koa-compose实现。</p>
<h2 id="源码简介"><a href="#源码简介" class="headerlink" title="源码简介"></a>源码简介</h2><h3 id="application-js"><a href="#application-js" class="headerlink" title="application.js"></a>application.js</h3><p>application.js是koa的入口文件，它向外导出了创建class实例的构造函数，它继承了events，这样就会赋予框架事件监听和事件触发的能力。application还暴露了一些常用的api，比如toJSON、listen、use等等。</p>
<p>listen的实现原理其实就是对http.createServer进行了一个封装，重点是这个函数中传入的callback，它里面包含了中间件的合并，上下文的处理，对res的特殊处理。</p>
<p>use是收集中间件，将多个中间件放入一个缓存队列中，然后通过koa-compose这个插件进行递归组合调用这一些列的中间件。</p>
<h3 id="context-js"><a href="#context-js" class="headerlink" title="context.js"></a>context.js</h3><p>这部分就是koa的应用上下文ctx,其实就一个简单的对象暴露，里面的重点在delegate，这个就是代理，这个就是为了开发者方便而设计的，比如我们要访问ctx.repsponse.status但是我们通过delegate，可以直接访问ctx.status访问到它。</p>
<h3 id="request-js、response-js"><a href="#request-js、response-js" class="headerlink" title="request.js、response.js"></a>request.js、response.js</h3><p>这两部分就是对原生的res、req的一些操作了，大量使用es6的get和set的一些语法，去取headers或者设置headers、还有设置body等等。</p>
<h2 id="功能模块"><a href="#功能模块" class="headerlink" title="功能模块"></a>功能模块</h2><h3 id="模块一"><a href="#模块一" class="headerlink" title="模块一"></a>模块一</h3><p><strong>封装node http server和创建Koa类构造函数</strong>。对http server进行了简单的封装和创建了一个可以生成koa实例的类class，这个类里还实现了app.use用来注册中间件和注册回调函数，app.listen用来开启服务器实例并传入callback回调函数；主要还是搭了一个简陋的架子。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Application</span> </span>&#123;    </span><br><span class="line">    <span class="keyword">constructor</span>() &#123;        </span><br><span class="line">        <span class="keyword">this</span>.callbackFunc;</span><br><span class="line">    &#125;</span><br><span class="line">    listen(port) &#123;        </span><br><span class="line">        <span class="keyword">let</span> server = http.createServer(<span class="keyword">this</span>.callback());</span><br><span class="line">        server.listen(port);</span><br><span class="line">    &#125;</span><br><span class="line">    use(fn) &#123;</span><br><span class="line">        <span class="keyword">this</span>.callbackFunc = fn;</span><br><span class="line">    &#125;</span><br><span class="line">    callback() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.callbackFunc(req, res);</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">module</span>.exports = Application;</span><br></pre></td></tr></table></figure>

<h3 id="模块二"><a href="#模块二" class="headerlink" title="模块二"></a>模块二</h3><p><strong>构造request、response、context对象</strong>。其中，context就是我们平时写koa代码时的ctx，它相当于一个全局的koa实例上下文this，它连接了request、response两个功能模块，并且暴露给koa的实例和中间件等回调函数的参数中，起到承上启下的作用。</p>
<p>request、response两个功能模块分别对node的原生request、response进行了一个功能的封装，使用了getter和setter属性，基于node的对象req/res对象封装koa的request/response对象。</p>
<p>第一步，我们要实现上下文对象ctx，里面绑定了很多请求和相应相关的数据和方法，例如ctx.path、ctx.query、ctx.body()等等。思路是：用户调用use方法时，把这个回调fn存起来，创建一个createContext函数用来创建上下文，创建一个handleRequest函数用来处理请求，用户listen时将handleRequest放进createServer回调中，在函数内调用fn并将上下文对象传入，用户就得到了ctx。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Koa</span> <span class="keyword">extends</span> <span class="title">EventEmitter</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span> () &#123;</span><br><span class="line">    <span class="keyword">super</span>()</span><br><span class="line">    <span class="keyword">this</span>.fn</span><br><span class="line">    <span class="keyword">this</span>.context = context <span class="comment">// 将三个模块保存，全局的放到实例上</span></span><br><span class="line">    <span class="keyword">this</span>.request = request</span><br><span class="line">    <span class="keyword">this</span>.response = response</span><br><span class="line">  &#125;</span><br><span class="line">  use (fn) &#123;</span><br><span class="line">    <span class="keyword">this</span>.fn = fn</span><br><span class="line">  &#125;</span><br><span class="line">  createContext(req, res)&#123; <span class="comment">// 这是核心，创建ctx</span></span><br><span class="line">    <span class="comment">// 使用Object.create方法是为了继承this.context但在增加属性时不影响原对象;继承属性，且不影响原对象</span></span><br><span class="line">    <span class="keyword">const</span> ctx = <span class="built_in">Object</span>.create(<span class="keyword">this</span>.context)</span><br><span class="line">    <span class="keyword">const</span> request = ctx.request = <span class="built_in">Object</span>.create(<span class="keyword">this</span>.request)</span><br><span class="line">    <span class="keyword">const</span> response = ctx.response = <span class="built_in">Object</span>.create(<span class="keyword">this</span>.response)</span><br><span class="line">    <span class="comment">// 下面的操作是为了把ctx中的属性与request、response中的属性联系起来，其实就是将后两者中定义的方法挂载到ctx上</span></span><br><span class="line">    <span class="comment">//这样，我们后续就可以在ctx中用简洁的写法来访问各种属性了</span></span><br><span class="line">    ctx.req = request.req = response.req = req</span><br><span class="line">    ctx.res = request.res = response.res = res</span><br><span class="line">    request.ctx = response.ctx = ctx</span><br><span class="line">    request.response = response</span><br><span class="line">    response.request = request</span><br><span class="line">    <span class="keyword">return</span> ctx</span><br><span class="line">  &#125;</span><br><span class="line">  handleRequest(req,res)&#123; <span class="comment">// 创建一个处理请求的函数</span></span><br><span class="line">    <span class="keyword">let</span> ctx = <span class="keyword">this</span>.createContext(req, res) <span class="comment">// 创建ctx</span></span><br><span class="line">    <span class="keyword">this</span>.fn(ctx) <span class="comment">// 调用用户给的回调，把ctx还给用户使用。</span></span><br><span class="line">    res.end(ctx.body) <span class="comment">// ctx.body用来输出到页面，后面会说如何绑定数据到ctx.body</span></span><br><span class="line">  &#125;</span><br><span class="line">  listen (...args) &#123;</span><br><span class="line">    <span class="keyword">let</span> server = http.createServer(<span class="keyword">this</span>.handleRequest.bind(<span class="keyword">this</span>))<span class="comment">// 这里使用bind调用，以防this丢失</span></span><br><span class="line">    server.listen(...args)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>第二步：这个时候，我们可以直接通过ctx.request.req.url或者ctx.response.req.url来获取想要的值，但还是利用的原生属性方法，此时我们基于node的对象req/res对象封装koa的request/response对象；其实两者的本质都是一样的封装。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//request.js</span></span><br><span class="line"><span class="keyword">let</span> url = <span class="built_in">require</span>(<span class="string">'url'</span>)</span><br><span class="line"><span class="keyword">let</span> request = &#123;</span><br><span class="line">  <span class="keyword">get</span> url() &#123; <span class="comment">// 这样就可以用ctx.request.url上取值了，不用通过原生的req</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.req.url</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="keyword">get</span> path() &#123;</span><br><span class="line">    <span class="keyword">return</span> url.parse(<span class="keyword">this</span>.req.url).pathname</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="keyword">get</span> query() &#123;</span><br><span class="line">    <span class="keyword">return</span> url.parse(<span class="keyword">this</span>.req.url).query</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 。。。。。。</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">module</span>.exports = request</span><br><span class="line"></span><br><span class="line"><span class="comment">//response.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">    <span class="keyword">get</span> body() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>._body;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="keyword">set</span> body(data) &#123;</span><br><span class="line">        <span class="keyword">this</span>._body = data;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="keyword">get</span> status() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.res.statusCode;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="keyword">set</span> status(statusCode) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">typeof</span> statusCode !== <span class="string">'number'</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'something wrong!'</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">this</span>.res.statusCode = statusCode;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">以上代码实现了对koa的status的读取和设置，读取的时候返回的是基于原生的response对象的statusCode属性，而body的读取则是对this._body进行读写和操作。这里对body进行操作并没有使用原生的this.res.end，因为在我们编写koa代码的时候，会对body进行多次的读取和修改，所以真正返回浏览器信息的操作是在application.js里进行封装和操作。</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<p>第三步：这个时候我们可以通过ctx.request.url、ctx.request.path来取到值，但还是不够方便，我们想要实现ctx直接取值。这样我们利用context.js，将上面实现的那些方法直接挂载到ctx上面。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> proto = &#123;&#125;;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">delegateSet</span>(<span class="params">property, name</span>) </span>&#123;</span><br><span class="line">    proto.__defineSetter__(name, <span class="function"><span class="keyword">function</span> (<span class="params">val</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>[property][name] = val;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">delegateGet</span>(<span class="params">property, name</span>) </span>&#123;</span><br><span class="line">    proto.__defineGetter__(name, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>[property][name];</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">let</span> requestSet = [];</span><br><span class="line"><span class="keyword">let</span> requestGet = [<span class="string">'query'</span>];</span><br><span class="line"><span class="keyword">let</span> responseSet = [<span class="string">'body'</span>, <span class="string">'status'</span>];</span><br><span class="line"><span class="keyword">let</span> responseGet = responseSet;</span><br><span class="line">requestSet.forEach(<span class="function"><span class="params">ele</span> =&gt;</span> &#123;</span><br><span class="line">    delegateSet(<span class="string">'request'</span>, ele);</span><br><span class="line">&#125;);</span><br><span class="line">requestGet.forEach(<span class="function"><span class="params">ele</span> =&gt;</span> &#123;</span><br><span class="line">    delegateGet(<span class="string">'request'</span>, ele);</span><br><span class="line">&#125;);</span><br><span class="line">responseSet.forEach(<span class="function"><span class="params">ele</span> =&gt;</span> &#123;</span><br><span class="line">    delegateSet(<span class="string">'response'</span>, ele);</span><br><span class="line">&#125;);</span><br><span class="line">responseGet.forEach(<span class="function"><span class="params">ele</span> =&gt;</span> &#123;</span><br><span class="line">    delegateGet(<span class="string">'response'</span>, ele);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="built_in">module</span>.exports = proto;</span><br></pre></td></tr></table></figure>

<p>context.js文件主要是对常用的request和response方法进行挂载和代理，通过context.query直接代理了context.request.query，context.body和context.status代理了context.response.body与context.response.status。而context.request，context.response则会在application.js中挂载。</p>
<h3 id="模块三"><a href="#模块三" class="headerlink" title="模块三"></a>模块三</h3><p>目前为止我们已经成功实现了上下文context对象、 请求request对象和响应response对象模块，还差一个最重要的模块，就是<strong>koa的中间件模块</strong>，koa的中间件机制是一个剥洋葱式的模型，多个中间件通过use放进一个数组队列然后从外层开始执行，遇到next后进入队列中的下一个中间件，所有中间件执行完后开始回帧，执行队列中之前中间件中未执行的代码部分，这就是剥洋葱模型，koa的中间件机制。这样，开发者手中的请求数据会像仪仗队一样，乖乖的经过每一层中间件的检阅，最后响应给用户。既应付了复杂的操作，又避免了混乱的嵌套。除此之外，koa的中间件还支持异步，可以使用async/await。</p>
<p>第一步：<strong>让多个use的回调按照顺序排列成串</strong>。这里用到了数组和递归，每次use将当前函数存到一个数组中，最后按顺序执行。执行这一步用到一个<strong>compose</strong>函数，这个函数是重中之重。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">constructor</span> () &#123;</span><br><span class="line">    <span class="keyword">super</span>()</span><br><span class="line">    <span class="comment">// this.fn  改成：</span></span><br><span class="line">    <span class="keyword">this</span>.middlewares = [] <span class="comment">// 需要一个数组将每个中间件按顺序存放起来</span></span><br><span class="line">    <span class="keyword">this</span>.context = context</span><br><span class="line">    <span class="keyword">this</span>.request = request</span><br><span class="line">    <span class="keyword">this</span>.response = response</span><br><span class="line">&#125;</span><br><span class="line">use (fn) &#123;</span><br><span class="line">    <span class="comment">// this.fn = fn 改成：</span></span><br><span class="line">    <span class="keyword">this</span>.middlewares.push(fn) <span class="comment">// 每次use，把当前回调函数存进数组</span></span><br><span class="line">&#125;</span><br><span class="line">compose(middlewares, ctx)&#123; <span class="comment">// 简化版的compose，接收中间件数组、ctx对象作为参数</span></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">dispatch</span>(<span class="params">index</span>)</span>&#123; <span class="comment">// 利用递归函数将各中间件串联起来依次调用</span></span><br><span class="line">        <span class="keyword">if</span>(index === middlewares.length) <span class="keyword">return</span> <span class="comment">// 最后一次next不能执行，不然会报错</span></span><br><span class="line">        <span class="keyword">let</span> middleware = middlewares[index] <span class="comment">// 取当前应该被调用的函数</span></span><br><span class="line">        middleware(ctx, () =&gt; dispatch(index + <span class="number">1</span>)) <span class="comment">// 调用并传入ctx和下一个将被调用的函数，用户next()时执行该函数</span></span><br><span class="line">    &#125;</span><br><span class="line">    dispatch(<span class="number">0</span>)</span><br><span class="line">&#125;</span><br><span class="line">handleRequest(req,res)&#123;</span><br><span class="line">    res.statusCode = <span class="number">404</span></span><br><span class="line">    <span class="keyword">let</span> ctx = <span class="keyword">this</span>.createContext(req, res)</span><br><span class="line">    <span class="comment">// this.fn(ctx) 改成：</span></span><br><span class="line">    <span class="keyword">this</span>.compose(<span class="keyword">this</span>.middlewares, ctx) <span class="comment">// 调用compose，传入参数</span></span><br><span class="line">    <span class="keyword">if</span>(<span class="keyword">typeof</span> ctx.body == <span class="string">'object'</span>)&#123;</span><br><span class="line">        res.setHeader(<span class="string">'Content-Type'</span>, <span class="string">'application/json;charset=utf8'</span>)</span><br><span class="line">        res.end(<span class="built_in">JSON</span>.stringify(ctx.body))</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (ctx.body <span class="keyword">instanceof</span> Stream)&#123;</span><br><span class="line">        ctx.body.pipe(res)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> ctx.body === <span class="string">'string'</span> || Buffer.isBuffer(ctx.body)) &#123;</span><br><span class="line">        res.setHeader(<span class="string">'Content-Type'</span>, <span class="string">'text/htmlcharset=utf8'</span>)</span><br><span class="line">        res.end(ctx.body)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        res.end(<span class="string">'Not found'</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>第二步：把每个回调包装成<a href="https://juejin.im/post/6844903583234834445" target="_blank" rel="noopener">Promise</a>以实现异步，并在调用时then。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">compose(middlewares, ctx)&#123;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">dispatch</span>(<span class="params">index</span>)</span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(index === middlewares.length) <span class="keyword">return</span> <span class="built_in">Promise</span>.resolve() <span class="comment">// 若最后一个中间件，返回一个resolve的promise</span></span><br><span class="line">        <span class="keyword">let</span> middleware = middlewares[index]</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">Promise</span>.resolve(middleware(ctx, () =&gt; dispatch(index + <span class="number">1</span>))) <span class="comment">// 用Promise.resolve把中间件包起来</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dispatch(<span class="number">0</span>)</span><br><span class="line">&#125;</span><br><span class="line">handleRequest(req,res)&#123;</span><br><span class="line">    res.statusCode = <span class="number">404</span></span><br><span class="line">    <span class="keyword">let</span> ctx = <span class="keyword">this</span>.createContext(req, res)</span><br><span class="line">    <span class="keyword">let</span> fn = <span class="keyword">this</span>.compose(<span class="keyword">this</span>.middlewares, ctx)</span><br><span class="line">    fn.then(<span class="function"><span class="params">()</span> =&gt;</span> &#123; <span class="comment">// then了之后再进行判断</span></span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">typeof</span> ctx.body == <span class="string">'object'</span>)&#123;</span><br><span class="line">            res.setHeader(<span class="string">'Content-Type'</span>, <span class="string">'application/json;charset=utf8'</span>)</span><br><span class="line">            res.end(<span class="built_in">JSON</span>.stringify(ctx.body))</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (ctx.body <span class="keyword">instanceof</span> Stream)&#123;</span><br><span class="line">            ctx.body.pipe(res)</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> ctx.body === <span class="string">'string'</span> || Buffer.isBuffer(ctx.body)) &#123;</span><br><span class="line">            res.setHeader(<span class="string">'Content-Type'</span>, <span class="string">'text/htmlcharset=utf8'</span>)</span><br><span class="line">            res.end(ctx.body)</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            res.end(<span class="string">'Not found'</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;).catch(<span class="function"><span class="params">err</span> =&gt;</span> &#123; <span class="comment">// 监控错误发射error，用于app.on('error', (err) =&gt;&#123;&#125;)</span></span><br><span class="line">        <span class="keyword">this</span>.emit(<span class="string">'error'</span>, err)</span><br><span class="line">        res.statusCode = <span class="number">500</span></span><br><span class="line">        res.end(<span class="string">'server error'</span>)</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面是使用promise来封装的代码，后面用async和await可以让代码更加简洁。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">compose() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">async</span> ctx =&gt; &#123;</span><br><span class="line">            <span class="function"><span class="keyword">function</span> <span class="title">createNext</span>(<span class="params">middleware, oldNext</span>) </span>&#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">                    <span class="keyword">await</span> middleware(ctx, oldNext);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">let</span> len = <span class="keyword">this</span>.middlewares.length;</span><br><span class="line">            <span class="keyword">let</span> next = <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="built_in">Promise</span>.resolve();</span><br><span class="line">            &#125;;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">let</span> i = len - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">                <span class="keyword">let</span> currentMiddleware = <span class="keyword">this</span>.middlewares[i];</span><br><span class="line">                next = createNext(currentMiddleware, next);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">await</span> next();</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<h3 id="模块四"><a href="#模块四" class="headerlink" title="模块四"></a>模块四</h3><p>要实现一个基础框架，<strong>错误处理和捕获</strong>必不可少，一个健壮的框架，必须保证在发生错误的时候，能够捕获到错误和抛出的异常，并反馈出来，将错误信息发送到监控系统上进行反馈。</p>
<p>我们如果想要捕获promise的错误，只需要使用promise的catch方法，就可以把所有的中间件的异常全部捕获到，现在我们已经实现了中间件的错误异常捕获，但是我们还缺少框架层发生错误的捕获机制，我们希望我们的服务器实例能有错误事件的监听机制，通过on的监听函数就能订阅和监听框架层面上的错误，实现这个机制不难，使用nodejs原生events模块即可，events模块给我们提供了事件监听on函数和事件触发emit行为函数，一个发射事件，一个负责接收事件，我们只需要将koa的构造函数继承events模块即可，继承了events模块后，当我们创建koa实例的时候，加上on监听函数，</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> EventEmitter = <span class="built_in">require</span>(<span class="string">'events'</span>);</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Application</span> <span class="keyword">extends</span> <span class="title">EventEmitter</span> </span>&#123;&#125;</span><br><span class="line"><span class="keyword">let</span> app = <span class="keyword">new</span> Koa();</span><br><span class="line"></span><br><span class="line">app.on(<span class="string">'error'</span>, err =&gt; &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'error happends: '</span>, err.stack);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>这样我们就实现了框架层面上的错误的捕获和监听机制了。总结一下，错误处理和捕获，分中间件的错误处理捕获和框架层的错误处理捕获，中间件的错误处理用promise的catch，框架层面的错误处理用nodejs的原生模块events，这样我们就可以把一个服务器实例上的所有的错误异常全部捕获到了。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/05/14/react/react%20hooks/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/05/14/react/react%20hooks/" class="post-title-link" itemprop="url">react hooks初学</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-05-14 12:18:39" itemprop="dateCreated datePublished" datetime="2021-05-14T12:18:39+08:00">2021-05-14</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:20:52" itemprop="dateModified" datetime="2021-11-03T16:20:52+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/react/" itemprop="url" rel="index"><span itemprop="name">react</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>hooks的概念之类的就不赘述了，之前用react中的class来实现了一个todoList的效果，这里同样用hooks来实现一个这个效果的复现，通过实际行动来实现熟悉hooks的目的。同时，这里我们使用redux来实现react hooks中的数据状态控制。</p>
<h1 id="todoList"><a href="#todoList" class="headerlink" title="todoList"></a>todoList</h1><h2 id="reducer"><a href="#reducer" class="headerlink" title="reducer"></a>reducer</h2><p>在reducer中定义该模块中能对组件中数据进行的各类操作，先定义好action.type，之后直接使用即可。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;createStore&#125; <span class="keyword">from</span> <span class="string">"redux"</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">reducer</span>(<span class="params">state=[],action</span>)</span>&#123;</span><br><span class="line">    <span class="keyword">switch</span>(action.type)&#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"ADD"</span>:</span><br><span class="line">            <span class="comment">//console.log(action.val);</span></span><br><span class="line">            state.push(&#123;</span><br><span class="line">                id: <span class="built_in">Date</span>.now(),</span><br><span class="line">                txt: action.val,</span><br><span class="line">                done: <span class="literal">false</span></span><br><span class="line">            &#125;)</span><br><span class="line">            <span class="comment">//全局的数据部署以简洁为主，只和某个组件有关的数据分别在组件中定义即可</span></span><br><span class="line">            <span class="keyword">return</span> [...state];</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"DONE"</span>:</span><br><span class="line">            state.forEach(<span class="function"><span class="params">item</span>=&gt;</span>&#123;</span><br><span class="line">                <span class="keyword">if</span>(item.id === action.id)&#123;</span><br><span class="line">                    item.done = action.done      </span><br><span class="line">                &#125;</span><br><span class="line">            &#125;) </span><br><span class="line">            <span class="keyword">return</span> [...state];</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"REMOVE"</span>:</span><br><span class="line">            <span class="keyword">return</span> state.filter(<span class="function"><span class="params">item</span>=&gt;</span>(item.id !== action.id));</span><br><span class="line">        <span class="keyword">case</span> <span class="string">"EDIT"</span>:</span><br><span class="line">            state.forEach(<span class="function"><span class="params">item</span>=&gt;</span>&#123;</span><br><span class="line">                <span class="keyword">if</span>(item.id === action.id)&#123;</span><br><span class="line">                    item.txt = action.txt      </span><br><span class="line">                &#125;</span><br><span class="line">            &#125;)  </span><br><span class="line">            <span class="keyword">return</span>  [...state];   </span><br><span class="line">        <span class="keyword">case</span> <span class="string">"REMOVE_DONE"</span>:</span><br><span class="line">            <span class="keyword">return</span> state.filter(<span class="function"><span class="params">item</span>=&gt;</span>!item.done);          </span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> state;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> store = createStore(reducer);</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> store;</span><br></pre></td></tr></table></figure>

<h2 id="App-js"><a href="#App-js" class="headerlink" title="App.js"></a>App.js</h2><p>对整个项目各个模块进行简单的划分，</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">"react"</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">"./static/index.css"</span>;</span><br><span class="line"><span class="keyword">import</span> Title <span class="keyword">from</span> <span class="string">"./view/title"</span>;</span><br><span class="line"><span class="keyword">import</span> Todos <span class="keyword">from</span> <span class="string">"./view/todos"</span>;</span><br><span class="line"><span class="keyword">import</span> Create <span class="keyword">from</span> <span class="string">"./view/create"</span>;</span><br><span class="line"><span class="keyword">import</span> State <span class="keyword">from</span> <span class="string">"./view/state"</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;connect&#125; <span class="keyword">from</span> <span class="string">"react-redux"</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">App</span>(<span class="params">props</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> &#123;length&#125; = props;</span><br><span class="line">    <span class="keyword">return</span> (<span class="xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">"todoapp"</span>&gt;</span></span></span><br><span class="line"><span class="xml">        <span class="tag">&lt;<span class="name">Title</span>/&gt;</span></span></span><br><span class="line">        &lt;div className="content"&gt;</span><br><span class="line">            &lt;Create/&gt;</span><br><span class="line">            &#123;length&gt;0?&lt;Todos/&gt;:""&#125;</span><br><span class="line">            &#123;length&gt;0?&lt;State/&gt;:""&#125;</span><br><span class="line"><span class="xml">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> connect(<span class="function"><span class="params">state</span> =&gt;</span> (&#123;</span><br><span class="line">    length:state.length</span><br><span class="line">    <span class="comment">//箭头函数要返回一个对象，用括号将对象包裹起来</span></span><br><span class="line">&#125;))(App);</span><br></pre></td></tr></table></figure>

<h2 id="Create-js"><a href="#Create-js" class="headerlink" title="Create.js"></a>Create.js</h2><p>该组件实现用户在输入框中添加待办事项的功能。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">"react"</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;connect&#125; <span class="keyword">from</span> <span class="string">"react-redux"</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Create</span>(<span class="params">props</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> &#123;dispatch&#125; = props;</span><br><span class="line">    <span class="keyword">return</span> (<span class="xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">"create-todo"</span>&gt;</span></span></span><br><span class="line">        &lt;input</span><br><span class="line">            id="new-todo"</span><br><span class="line">            placeholder="你需要做什么"</span><br><span class="line">            autoComplete="off"</span><br><span class="line">            type="text"</span><br><span class="line">            defaultValue=''</span><br><span class="line">            onKeyDown=&#123;(e) =&gt; &#123;</span><br><span class="line">                if (e.keyCode == 13) &#123;</span><br><span class="line">                    dispatch(&#123;</span><br><span class="line">                        type: "ADD",</span><br><span class="line">                        val: e.target.value</span><br><span class="line">                    &#125;);</span><br><span class="line">                    e.target.value = "";</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;&#125;</span><br><span class="line"><span class="xml">        /&gt;</span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> connect()(Create);</span><br></pre></td></tr></table></figure>

<h2 id="Todos-js"><a href="#Todos-js" class="headerlink" title="Todos.js"></a>Todos.js</h2><p>在这里，我们接受app中传递过来的数据，同时对于每一项todo都以每一列来单独进行处理，这里我们将其封装在li.js中，这样就只用在todos.js中将内容分别分发到li.js中即可。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">"react"</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;connect&#125; <span class="keyword">from</span> <span class="string">"react-redux"</span>;</span><br><span class="line"><span class="keyword">import</span> Li <span class="keyword">from</span> <span class="string">'./li'</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Todos</span>(<span class="params">props</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> &#123;state, dispatch&#125; = props;</span><br><span class="line">    <span class="keyword">return</span> (<span class="xml"><span class="tag">&lt;<span class="name">ul</span> <span class="attr">id</span>=<span class="string">"todo-list"</span>&gt;</span></span></span><br><span class="line"><span class="xml">        &#123;state.map(item =&gt; <span class="tag">&lt;<span class="name">Li</span> <span class="attr">data</span>=<span class="string">&#123;item&#125;</span> <span class="attr">key</span>=<span class="string">&#123;item.id&#125;</span> <span class="attr">dispatch</span>=<span class="string">&#123;dispatch&#125;/</span>&gt;</span>)&#125;</span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span>)</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> connect(<span class="function">(<span class="params">state</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;state&#125;</span><br><span class="line">&#125;)(Todos);</span><br></pre></td></tr></table></figure>

<h2 id="li-js"><a href="#li-js" class="headerlink" title="li.js"></a>li.js</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React, &#123;useEffect, useState, useRef&#125; <span class="keyword">from</span> <span class="string">"react"</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Li</span>(<span class="params">props</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> &#123;data, dispatch&#125; = props;</span><br><span class="line">    <span class="keyword">let</span> &#123;id, txt, done&#125; = data;</span><br><span class="line">    <span class="keyword">const</span> [edit, setEdit] = useState(<span class="literal">false</span>);<span class="comment">//在edit中保存的数据只需要一个布尔值即可</span></span><br><span class="line">    <span class="keyword">const</span> [inputVal, setInputVal] = useState(txt);</span><br><span class="line">    <span class="comment">//这样设置，txt做了修改之后，inputVal会自动和txt进行同步.</span></span><br><span class="line">    <span class="keyword">const</span> input = useRef();</span><br><span class="line">    useEffect(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (edit) &#123;</span><br><span class="line">            input.current.focus();<span class="comment">//当你要编辑时，则对当前元素进行聚焦</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;, [edit]);</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">        &lt;li className=&#123;edit?<span class="string">"editing"</span>:<span class="string">""</span>&#125;&gt;</span><br><span class="line">            &lt;div className=&#123;<span class="string">"todo "</span> + (done? <span class="string">"done"</span>:<span class="string">""</span>)&#125;&gt;</span><br><span class="line">                &lt;div className=<span class="string">"display"</span>&gt;</span><br><span class="line">                    &lt;input</span><br><span class="line">                        className=<span class="string">"check"</span></span><br><span class="line">                        type=<span class="string">"checkbox"</span></span><br><span class="line">                        checked=&#123;done&#125;</span><br><span class="line">                        onChange=&#123;(&#123;target&#125;) =&gt; &#123;</span><br><span class="line">                            dispatch(&#123;</span><br><span class="line">                                type: <span class="string">"DONE"</span>,</span><br><span class="line">                                id,</span><br><span class="line">                                done:target.checked,</span><br><span class="line">                            &#125;);</span><br><span class="line">                        &#125;&#125;</span><br><span class="line">                    /&gt;</span><br><span class="line">                    &lt;div</span><br><span class="line">                        className=<span class="string">"todo-content"</span></span><br><span class="line">                        onDoubleClick=&#123;() =&gt; &#123;</span><br><span class="line">                            setEdit(<span class="literal">true</span>);</span><br><span class="line">                        &#125;&#125;</span><br><span class="line">                    &gt;&#123;txt&#125;</span><br><span class="line">                    &lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">                    &lt;span</span></span><br><span class="line"><span class="regexp">                        className="todo-destroy"</span></span><br><span class="line"><span class="regexp">                        onClick=&#123;() =&gt; &#123;</span></span><br><span class="line"><span class="regexp">                            dispatch(&#123;</span></span><br><span class="line"><span class="regexp">                                type:"REMOVE",</span></span><br><span class="line"><span class="regexp">                                id</span></span><br><span class="line"><span class="regexp">                            &#125;);</span></span><br><span class="line"><span class="regexp">                        &#125;&#125;&gt;</span></span><br><span class="line"><span class="regexp">                    &lt;/</span>span&gt;</span><br><span class="line">                &lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">                &lt;div className="edit"&gt;</span></span><br><span class="line"><span class="regexp">                    &lt;input</span></span><br><span class="line"><span class="regexp">                        className="todo-input"</span></span><br><span class="line"><span class="regexp">                        type="text"</span></span><br><span class="line"><span class="regexp">                        value=&#123;inputVal&#125;</span></span><br><span class="line"><span class="regexp">                        ref=&#123;input&#125;</span></span><br><span class="line"><span class="regexp">                        onChange=&#123;(&#123;target&#125;) =&gt; &#123;</span></span><br><span class="line"><span class="regexp">                            setInputVal(target.value);</span></span><br><span class="line"><span class="regexp">                        &#125;&#125;</span></span><br><span class="line"><span class="regexp">                        onBlur = &#123;() =&gt; &#123;</span></span><br><span class="line"><span class="regexp">                            setEdit(false);</span></span><br><span class="line"><span class="regexp">                            if (inputVal.trim()) &#123;</span></span><br><span class="line"><span class="regexp">                                /</span><span class="regexp">/当输入为空时，则还是将之前保存的txt存进</span></span><br><span class="line"><span class="regexp">                                dispatch(&#123;</span></span><br><span class="line"><span class="regexp">                                    type:"EDIT",</span></span><br><span class="line"><span class="regexp">                                    id,</span></span><br><span class="line"><span class="regexp">                                    txt: inputVal,</span></span><br><span class="line"><span class="regexp">                                &#125;);</span></span><br><span class="line"><span class="regexp">                            &#125; else &#123;</span></span><br><span class="line"><span class="regexp">                                setInputVal(txt);</span></span><br><span class="line"><span class="regexp">                            &#125;</span></span><br><span class="line"><span class="regexp">                        &#125;&#125;</span></span><br><span class="line"><span class="regexp">                        /</span><span class="regexp">/失去焦点时的事件触发</span></span><br><span class="line"><span class="regexp">                    /</span>&gt;</span><br><span class="line">                &lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">            &lt;/</span>div&gt;</span><br><span class="line">        &lt;<span class="regexp">/li&gt;</span></span><br><span class="line"><span class="regexp">    )</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">export default Li;</span></span><br></pre></td></tr></table></figure>

<h2 id="state-js"><a href="#state-js" class="headerlink" title="state.js"></a>state.js</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;connect&#125; <span class="keyword">from</span> <span class="string">"react-redux"</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">State</span> (<span class="params">props</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> &#123;unDoneLength, doneLength, dispatch&#125; = props;</span><br><span class="line">    <span class="keyword">return</span> (<span class="xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">"todo-stats"</span>&gt;</span></span></span><br><span class="line">        &lt;span className="todo-count"&gt;</span><br><span class="line">            &lt;span className="number"&gt;&#123;unDoneLength&#125;&lt;/span&gt; &lt;span className="word"&gt;项待完成&lt;/span&gt;</span><br><span class="line"><span class="xml">        <span class="tag">&lt;/<span class="name">span</span>&gt;</span></span></span><br><span class="line"><span class="xml">        &#123;</span></span><br><span class="line">            doneLength&gt;0?(&lt;span className="todo-clear"&gt;</span><br><span class="line">                &lt;a</span><br><span class="line">                    href="#"</span><br><span class="line">                    onClick=&#123;() =&gt; &#123;</span><br><span class="line">                        dispatch(&#123;</span><br><span class="line">                            type: "REMOVE_DONE"</span><br><span class="line">                        &#125;);</span><br><span class="line">                    &#125;&#125;&gt;</span><br><span class="line">                    Clear &lt;span&gt;&#123;doneLength&#125;&lt;/span&gt;已完成事项</span><br><span class="line">                &lt;/a&gt;</span><br><span class="line"><span class="xml">            <span class="tag">&lt;/<span class="name">span</span>&gt;</span>):""</span></span><br><span class="line"><span class="xml">        &#125;</span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> connect(<span class="function"><span class="params">state</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> unDoneLength = state.filter(<span class="function"><span class="params">item</span> =&gt;</span> !item.done).length;</span><br><span class="line">    <span class="keyword">let</span> doneLength =  state.length  - unDoneLength;</span><br><span class="line">    <span class="comment">//对state数据进行处理，从而确定传递给组件的props数据到底是什么样的，</span></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        unDoneLength,</span><br><span class="line">        doneLength</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)(State);</span><br></pre></td></tr></table></figure>


      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/05/10/react/react%E5%88%9D%E5%AD%A6/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/05/10/react/react%E5%88%9D%E5%AD%A6/" class="post-title-link" itemprop="url">react初学</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-05-10 12:18:39" itemprop="dateCreated datePublished" datetime="2021-05-10T12:18:39+08:00">2021-05-10</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:20:56" itemprop="dateModified" datetime="2021-11-03T16:20:56+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/react/" itemprop="url" rel="index"><span itemprop="name">react</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>之前项目都是用的vue2.0，目前打算自己看看react，想用react自己实现一个简单的项目吧，因为用过其他框架，也不算是零基础吧。但可能本篇的内容也算是讲下自己初学react时遇到的坑吧，内容比较简单，最后打算自己用react封装一个todoList的组件就算完成了react第一步的学习吧，之后再开始理解并运用react hooks。</p>
<h1 id="基本知识"><a href="#基本知识" class="headerlink" title="基本知识"></a>基本知识</h1><h2 id="setState"><a href="#setState" class="headerlink" title="setState"></a>setState</h2><h3 id="使用注意"><a href="#使用注意" class="headerlink" title="使用注意"></a>使用注意</h3><p><strong>1、不要直接修改 state(状态)。</strong>因为和vue的双向绑定不同，这样是不会触发界面的重新渲染的，一定要使用this.setState()；需要注意的是唯一可以分配 this.state 的地方是构造函数。</p>
<p><strong>2、state(状态) 更新可能是异步的。</strong>React 为了优化性能，有可能会将多个 setState() 调用合并为一次更新。因为this.props和this.state 可能是异步更新的，你不能依赖他们的值计算下一个state(状态)。我们并不能通过上述代码得到想要的值，为了弥补这个问题，使用另一种 setState() 的形式，接受一个函数。这个函数将接收前一个状态作为第一个参数，应用更新时的 props 作为第二个参数，代码如下</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">this</span>.setState(&#123;</span><br><span class="line">  counter: <span class="keyword">this</span>.state.counter + <span class="keyword">this</span>.props.increment,</span><br><span class="line">&#125;);</span><br><span class="line"><span class="keyword">this</span>.setState(<span class="function">(<span class="params">prevState, props</span>) =&gt;</span> (&#123;</span><br><span class="line">  counter: prevState.counter + props.increment</span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure>

<p><strong>3.state(状态)更新会被合并。</strong>当你调用 setState()， React 将合并你提供的对象到当前的状态中。所以当State是一个多键值的结构时，可以单独更新其中的一个，此时会进行“差分”更新，不会影响其他的属性值。</p>
<h3 id="props-与-state-的区别"><a href="#props-与-state-的区别" class="headerlink" title="props 与 state 的区别"></a>props 与 state 的区别</h3><p>state 的主要作用是用于组件保存、控制、修改自己的可变状态，在组件内部进行初始化，也可以在组件内部进行修改，但是组件外部不能修改组件的 state。</p>
<p>props 的主要作用是让使用该组件的父组件可以传入参数来配置该组件，它是外部传进来的配置参数，组件内部无法控制也无法修改<br>state 和 props 都可以决定组件的外观和显示状态。通常，props 做为不变数据或者初始化数据传递给组件，可变状态使用 state</p>
<h3 id="setState-的异步更新"><a href="#setState-的异步更新" class="headerlink" title="setState()的异步更新"></a>setState()的异步更新</h3><p><strong>1.执行setState()之后干了什么？</strong></p>
<p>setState()方法通过一个队列机制实现state更新，当执行setState()的时候，会将需要更新的state合并之后放入状态队列，而不会立即更新this.state(可以和浏览器的事件队列类比)。如果我们不使用setState而是使用this.state.key来修改，将不会触发组件的re-render。如果将this.state赋值给一个新的对象引用，那么其他不在对象上的state将不会被放入状态队列中，当下次调用setState()并对状态队列进行合并时，直接造成了state丢失。</p>
<p><strong>2.setState()可以接受一个函数作为参数？</strong></p>
<p>setState() 不仅能够接受一个对象作为参数，还能够接受一个函数作为参数。函数的参数即为 state 的前一个状态以及 props。</p>
<p>React文档中对setState的说明如下：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">void setState (function|object nextState,[function callback])</span><br></pre></td></tr></table></figure>

<p>上述代码的第二个参数是一个回调函数，在setState() 的异步操作结束并且组件已经重新渲染的时候执行。换句话说，我们可以通过这个回调来拿到更新的state的值。</p>
<p><strong>3.执行setState()后能拿到最新的state值吗？</strong></p>
<p>以前在写代码时候，总是遇到明明执行过setState()，但是state的值却不是最新的，那么如何解决这个问题呢？</p>
<p>因为setState()函数接受两个参数，一个是一个对象，就是设置的状态，还有一个是一个回调函数，是在设置状态成功之后执行的，所以我们可以通过回调拿到最新的state值。代码如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">updateData = <span class="function">(<span class="params">newData</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">this</span>.setState(</span><br><span class="line">        &#123; <span class="attr">data</span>: newData &#125;,</span><br><span class="line">        () =&gt; &#123;</span><br><span class="line">            <span class="comment">//这里打印的是最新的state值</span></span><br><span class="line">            <span class="built_in">console</span>.log(that.state.data);</span><br><span class="line">        &#125;</span><br><span class="line">    );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>4.setState()一定是异步更新吗？</strong> </p>
<p>我们先来看看下面的代码：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">incrementMultiple</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">      <span class="keyword">this</span>.setState(&#123;<span class="attr">count</span>: <span class="keyword">this</span>.state.count + <span class="number">1</span>&#125;);</span><br><span class="line">      <span class="keyword">this</span>.setState(&#123;<span class="attr">count</span>: <span class="keyword">this</span>.state.count + <span class="number">1</span>&#125;);</span><br><span class="line">      <span class="keyword">this</span>.setState(&#123;<span class="attr">count</span>: <span class="keyword">this</span>.state.count + <span class="number">1</span>&#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>直观上来看，当上面的 incrementMultiple 函数被调用时，组件状态的 count 值被增加了3次，每次增加1，那最后 count 被增加了3。但是，实际上的结果只给 state 增加了1。</p>
<p>事实上，setState 方法与包含在其中的执行是一个很复杂的过程，从 React 最初的版本到现在，也有无数次的修改。它的工作除了要更动 this.state 之外，还要负责触发重新渲染，这里面要经过 React 核心 diff 算法，最终才能决定是否要进行重渲染，以及如何渲染。而且为了批次与效能的理由，多个 setState 呼叫有可能在执行过程中还需要被合并，所以它被设计以延时的来进行执行是相当合理的。</p>
<p>在 React 的 setState 函数实现中，会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到队列中回头再说，而 isBatchingUpdates 默认是 false，也就表示 setState 会同步更新 this.state，但是，有一个函数 batchedUpdates，这个函数会把 isBatchingUpdates 修改为 true，而当 React 在调用事件处理函数之前就会调用这个 batchedUpdates，造成的后果，就是由 React 控制的事件处理过程 setState 不会同步更新 this.state。</p>
<p>也就是说，在 React 控制之外的情况， setState 会同步更新 this.state！但大部份的使用情况下，我们都是使用了 React 库中的表单组件，例如 select、input、button 等等，它们都是 React 库中人造的组件与事件，是处于 React 库的控制之下，比如组件原色 onClick 都是经过 React 包装。在这个情况下，setState 就会以异步的方式执行。</p>
<h2 id="事件处理"><a href="#事件处理" class="headerlink" title="事件处理"></a>事件处理</h2><p>1、使用 JSX 语法时你需要传入一个函数作为事件处理函数，而不是一个字符串。</p>
<p>2、在 React 中另一个不同点是你不能通过返回 <code>false</code> 的方式阻止默认行为。你必须显式的使用 <code>preventDefault</code> 。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;activateLasers&#125;</span>&gt;</span></span><br><span class="line">  Activate Lasers</span><br><span class="line"><span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>3、你必须谨慎对待 JSX 回调函数中的 <code>this</code>，在 JavaScript 中，class 的方法默认不会<a href="https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind" target="_blank" rel="noopener">绑定</a> <code>this</code>。如果你忘记绑定 <code>this.handleClick</code> 并把它传入了 <code>onClick</code>，当你调用这个函数的时候 <code>this</code> 的值为 <code>undefined</code>。</p>
<p>4、在循环中，通常我们会为事件处理函数传递额外的参数。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;(e)</span> =&gt;</span> this.deleteRow(id, e)&#125;&gt;Delete Row<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.deleteRow.bind(this,</span> <span class="attr">id</span>)&#125;&gt;</span>Delete Row<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure>

<h1 id="ToDoList实践"><a href="#ToDoList实践" class="headerlink" title="ToDoList实践"></a>ToDoList实践</h1><p>该组件提供的主要功能就是，用户可以在一个输入框中输入toDo的事项，然后可以在后面的列表中进行显示，同时对于已经完成的事项，可以进行不同的渲染显示。根据数据驱动视图的逻辑，构建步骤如下：1、确定数据格式；2、根据数据完成列表的渲染；3、完成添加功能；4、完成单项错误功能。</p>
<h2 id="APP-js"><a href="#APP-js" class="headerlink" title="APP.js"></a>APP.js</h2><p>我们先实现数据视图的渲染，之后再新建方法来进行各种操作方法的定义与使用。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React,&#123;PureComponent&#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> Title <span class="keyword">from</span> <span class="string">'./title'</span>;</span><br><span class="line"><span class="keyword">import</span> Create <span class="keyword">from</span> <span class="string">'./create'</span>;</span><br><span class="line"><span class="keyword">import</span> Todos <span class="keyword">from</span> <span class="string">'./todos'</span>;</span><br><span class="line"><span class="keyword">import</span> State <span class="keyword">from</span> <span class="string">'./state'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">"./index.css"</span>;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">  数据驱动视图</span></span><br><span class="line"><span class="comment">   1. 确定数据格式</span></span><br><span class="line"><span class="comment">   2. 根据数据完成列表的渲染</span></span><br><span class="line"><span class="comment">   3. 完成添加功能</span></span><br><span class="line"><span class="comment">   4. 完成单项错误</span></span><br><span class="line"><span class="comment">   data:[</span></span><br><span class="line"><span class="comment">      &#123;</span></span><br><span class="line"><span class="comment">        id: 0,</span></span><br><span class="line"><span class="comment">        txt: "数据内容",</span></span><br><span class="line"><span class="comment">        done: false</span></span><br><span class="line"><span class="comment">      &#125;</span></span><br><span class="line"><span class="comment">   ]</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">PureComponent</span> </span>&#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">      data:[</span><br><span class="line">          &#123;</span><br><span class="line">            id: <span class="number">0</span>,</span><br><span class="line">            txt: <span class="string">"数据内容"</span>,</span><br><span class="line">            done: <span class="literal">false</span></span><br><span class="line">          &#125;</span><br><span class="line">      ]</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 添加数据</span></span><br><span class="line">  addData=<span class="function">(<span class="params">txt</span>)=&gt;</span>&#123;</span><br><span class="line">    <span class="keyword">let</span> &#123;data&#125; = <span class="keyword">this</span>.state;</span><br><span class="line">    data.unshift(&#123;</span><br><span class="line">      id: <span class="built_in">Date</span>.now(),<span class="comment">//利用获取当前的时间，来确保不重名，id用symbol的话就没办法后序对比</span></span><br><span class="line">      txt,</span><br><span class="line">      done: <span class="literal">false</span></span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">this</span>.setState(&#123;</span><br><span class="line">      data:[...data]</span><br><span class="line"><span class="comment">//pureComponent做的是浅对比，当直接用data时，发现路径没有改变，就不会有视图的更新；因此一定要返回新对象</span></span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 修改完成状态</span></span><br><span class="line">  changeDone=<span class="function">(<span class="params">id,done</span>)=&gt;</span>&#123;</span><br><span class="line">    <span class="keyword">let</span> &#123;data&#125; = <span class="keyword">this</span>.state;</span><br><span class="line">    data.forEach(<span class="function"><span class="params">item</span>=&gt;</span>&#123;</span><br><span class="line">      <span class="keyword">if</span>(item.id === id)&#123;</span><br><span class="line">        item.done = done;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">this</span>.setState(&#123;</span><br><span class="line">      data:data.map(<span class="function"><span class="params">item</span>=&gt;</span>(&#123;...item&#125;))</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 修改事项信息</span></span><br><span class="line">  editTxt=<span class="function">(<span class="params">id,txt</span>)=&gt;</span>&#123;</span><br><span class="line">    <span class="keyword">let</span> &#123;data&#125; = <span class="keyword">this</span>.state;</span><br><span class="line">    <span class="built_in">console</span>.log(id,txt);</span><br><span class="line">    data.forEach(<span class="function"><span class="params">item</span>=&gt;</span>&#123;</span><br><span class="line">      <span class="keyword">if</span>(item.id === id)&#123;</span><br><span class="line">        item.txt = txt;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">this</span>.setState(&#123;</span><br><span class="line">      data:data.map(<span class="function"><span class="params">item</span>=&gt;</span>(&#123;...item&#125;))</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 删除单项</span></span><br><span class="line">  remove=<span class="function">(<span class="params">id</span>)=&gt;</span>&#123;</span><br><span class="line">    <span class="keyword">let</span> &#123;data&#125; = <span class="keyword">this</span>.state;</span><br><span class="line">    data = data.filter(<span class="function"><span class="params">item</span>=&gt;</span>item.id!==id);</span><br><span class="line">    <span class="keyword">this</span>.setState(&#123;</span><br><span class="line">      data</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">//删除已完成的</span></span><br><span class="line">  removeDone=<span class="function"><span class="params">()</span>=&gt;</span>&#123;</span><br><span class="line">    <span class="keyword">let</span> &#123;data&#125; = <span class="keyword">this</span>.state;</span><br><span class="line">    data = data.filter(<span class="function"><span class="params">item</span>=&gt;</span>!item.done);</span><br><span class="line">    <span class="keyword">this</span>.setState(&#123;</span><br><span class="line">      data</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  render()&#123;</span><br><span class="line">    <span class="keyword">let</span> &#123;data&#125; = <span class="keyword">this</span>.state; </span><br><span class="line">    <span class="keyword">return</span> (<span class="xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">"todoapp"</span>&gt;</span></span></span><br><span class="line"><span class="xml">        <span class="tag">&lt;<span class="name">Title</span> /&gt;</span></span></span><br><span class="line">        &lt;div className="content"&gt;</span><br><span class="line">            &lt;Create addData=&#123;this.addData&#125; /&gt;    </span><br><span class="line">            &#123;data.length&lt;1?"":[</span><br><span class="line">                &lt;Todos</span><br><span class="line">                    key=&#123;1&#125;</span><br><span class="line">                    data=&#123;data&#125; </span><br><span class="line">                    changeDone=&#123;this.changeDone&#125; </span><br><span class="line">                    editTxt=&#123;this.editTxt&#125;</span><br><span class="line">                    remove = &#123;this.remove&#125;</span><br><span class="line">                /&gt;,</span><br><span class="line">                &lt;State </span><br><span class="line">                  data=&#123;data&#125; </span><br><span class="line">                  key=&#123;2&#125;</span><br><span class="line">                  removeDone=&#123;this.removeDone&#125;</span><br><span class="line">                /&gt;</span><br><span class="line">            ]&#125;</span><br><span class="line">        &lt;/div&gt;</span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span>)</span></span><br><span class="line"><span class="xml">  &#125;</span></span><br><span class="line"><span class="xml">&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="xml">export default App;</span></span><br></pre></td></tr></table></figure>

<h2 id="create-js"><a href="#create-js" class="headerlink" title="create.js"></a>create.js</h2><p>Create是视图中输入框的控件，用户使用其来对todolist列表增加项目数。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React,&#123;PureComponent&#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Create</span> <span class="keyword">extends</span> <span class="title">PureComponent</span> </span>&#123;</span><br><span class="line">  state=&#123;</span><br><span class="line">    val:<span class="string">""</span></span><br><span class="line">  &#125;</span><br><span class="line">  render()&#123;</span><br><span class="line">    <span class="keyword">let</span> &#123;addData&#125; = <span class="keyword">this</span>.props;</span><br><span class="line">    <span class="keyword">return</span> (<span class="xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">"create-todo"</span>&gt;</span></span></span><br><span class="line">        &lt;input </span><br><span class="line">          id="new-todo" </span><br><span class="line">          placeholder="What needs to be done?" </span><br><span class="line">          autoComplete="off"</span><br><span class="line">          type="text" </span><br><span class="line">          value=&#123;this.state.val&#125;//通过这样的方式，让value变得受控</span><br><span class="line">          onChange=&#123;(e)=&gt;&#123;</span><br><span class="line">            this.setState(&#123;</span><br><span class="line">              val:e.target.value</span><br><span class="line">            &#125;)</span><br><span class="line">          &#125;&#125;</span><br><span class="line">          onKeyDown=&#123;(e)=&gt;&#123;</span><br><span class="line">            let &#123;val&#125; = this.state</span><br><span class="line">            if(e.keyCode === 13)&#123;</span><br><span class="line">              if(!val.trim())&#123;</span><br><span class="line">                  alert("请输入内容");</span><br><span class="line">                  return ;</span><br><span class="line">              &#125;</span><br><span class="line">              addData(val); </span><br><span class="line">              this.setState(&#123;</span><br><span class="line">                val:""</span><br><span class="line">              &#125;)</span><br><span class="line">            &#125;</span><br><span class="line">           // console.log(e.keyCode,val);</span><br><span class="line">          &#125;&#125;</span><br><span class="line"><span class="xml">        /&gt;</span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> Create;</span><br></pre></td></tr></table></figure>



<h2 id="todos-js"><a href="#todos-js" class="headerlink" title="todos.js"></a>todos.js</h2><p>在这里，我们接受app中传递过来的数据，同时对于每一项todo都以每一列来单独进行处理，这里我们将其封装在li.js中，这样就只用在todos.js中将内容分别分发到li.js中即可。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React,&#123;PureComponent&#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> Li <span class="keyword">from</span> <span class="string">'./li'</span>;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Todos</span> <span class="keyword">extends</span> <span class="title">PureComponent</span> </span>&#123;</span><br><span class="line">  render()&#123;</span><br><span class="line">    <span class="comment">//console.log(this.props.data);</span></span><br><span class="line">    <span class="comment">//console.log(11); </span></span><br><span class="line">    <span class="keyword">let</span> &#123;data&#125; = <span class="keyword">this</span>.props;</span><br><span class="line">    <span class="keyword">return</span> (<span class="xml"><span class="tag">&lt;<span class="name">ul</span> <span class="attr">id</span>=<span class="string">"todo-list"</span>&gt;</span></span></span><br><span class="line"><span class="xml">            //这里需要注意的是，批量生成一定要有key值，这里我们使用数据的key属性</span></span><br><span class="line"><span class="xml">        &#123;data.map(item =&gt; <span class="tag">&lt;<span class="name">Li</span> <span class="attr">key</span>=<span class="string">&#123;item.id&#125;</span> &#123;<span class="attr">...this.props</span>&#125; <span class="attr">data</span>=<span class="string">&#123;item&#125;/</span>&gt;</span>)&#125;</span></span><br><span class="line"><span class="xml">    <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> Todos;</span><br></pre></td></tr></table></figure>

<h2 id="li-js"><a href="#li-js" class="headerlink" title="li.js"></a>li.js</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React,&#123;PureComponent,createRef&#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Li</span> <span class="keyword">extends</span> <span class="title">PureComponent</span> </span>&#123;</span><br><span class="line">  state=&#123;</span><br><span class="line">      edit:<span class="literal">false</span>,</span><br><span class="line">      ref: createRef(),</span><br><span class="line">      val:<span class="string">""</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">static</span> getDerivedStateFromProps(props, state)&#123;</span><br><span class="line">    <span class="comment">//console.log(props,state); </span></span><br><span class="line">    <span class="comment">//console.log(props, state);</span></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">        1. 挂载时：同步 state 中的val 和 props.txt</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">    <span class="keyword">if</span>(!state.edit)&#123;</span><br><span class="line">        state.val = props.data.txt;</span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  componentDidUpdate(prevPorps,prevState)&#123;</span><br><span class="line">      <span class="comment">// 组件更新完成之后，判断是否需要给 input 获得焦点</span></span><br><span class="line">      <span class="keyword">if</span>(<span class="keyword">this</span>.state.edit&amp;&amp;!prevState.edit)&#123;</span><br><span class="line">            <span class="comment">// 让输入框获得焦点</span></span><br><span class="line">            <span class="comment">//console.log(this.refs);</span></span><br><span class="line">            <span class="comment">//this.refs.txt.select();</span></span><br><span class="line">            <span class="comment">//console.log();</span></span><br><span class="line">            <span class="keyword">this</span>.state.ref.current.select();</span><br><span class="line">      &#125;</span><br><span class="line">  &#125;  </span><br><span class="line">  render()&#123;</span><br><span class="line">     <span class="keyword">let</span> &#123;data,changeDone,editTxt,remove&#125; = <span class="keyword">this</span>.props;</span><br><span class="line">     <span class="keyword">let</span> &#123;done,txt,id&#125; = data;</span><br><span class="line">     <span class="keyword">let</span> &#123;edit,val&#125; = <span class="keyword">this</span>.state; </span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">        &lt;li className=&#123;edit?<span class="string">"editing"</span>:<span class="string">""</span>&#125;&gt;</span><br><span class="line">            &lt;div className=&#123;<span class="string">"todo "</span> + (done?<span class="string">"done"</span>:<span class="string">""</span>)&#125;&gt;</span><br><span class="line">                &lt;div className=<span class="string">"display"</span>&gt;</span><br><span class="line">                    &lt;input </span><br><span class="line">                        className=<span class="string">"check"</span> </span><br><span class="line">                        type=<span class="string">"checkbox"</span> </span><br><span class="line">                        checked=&#123;done&#125;</span><br><span class="line">                        onChange=&#123;(e)=&gt;&#123;</span><br><span class="line">                            <span class="built_in">console</span>.log(e.target.checked);</span><br><span class="line">                            changeDone(id,e.target.checked);</span><br><span class="line">                        &#125;&#125;</span><br><span class="line">                    /&gt;</span><br><span class="line">                    &lt;div </span><br><span class="line">                        className=<span class="string">"todo-content"</span></span><br><span class="line">                        onDoubleClick=&#123;()=&gt;&#123;</span><br><span class="line">                            <span class="keyword">this</span>.setState(&#123;</span><br><span class="line">                                edit:<span class="literal">true</span></span><br><span class="line">                            &#125;)</span><br><span class="line">                        &#125;&#125;</span><br><span class="line">                    &gt;&#123;txt&#125;&lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">                    &lt;span </span></span><br><span class="line"><span class="regexp">                        className="todo-destroy"</span></span><br><span class="line"><span class="regexp">                        onClick=&#123;()=&gt;&#123;</span></span><br><span class="line"><span class="regexp">                            remove(id);</span></span><br><span class="line"><span class="regexp">                        &#125;&#125;</span></span><br><span class="line"><span class="regexp">                    &gt;&lt;/</span>span&gt;</span><br><span class="line">                &lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">                &lt;div className="edit"&gt;</span></span><br><span class="line"><span class="regexp">                    &#123;/</span>* <span class="xml"><span class="tag">&lt;<span class="name">input</span> </span></span></span><br><span class="line"><span class="xml">                        ref="txt"</span></span><br><span class="line"><span class="xml">                        className="todo-input" </span></span><br><span class="line"><span class="xml">                        type="text" </span></span><br><span class="line"><span class="xml">                        value=&#123;txt&#125;</span></span><br><span class="line"><span class="xml">                        onChange=&#123;(e)=&gt;&#123;</span></span><br><span class="line"><span class="xml">                            editTxt(id,e.target.value)</span></span><br><span class="line"><span class="xml">                        &#125;&#125;</span></span><br><span class="line"><span class="xml">                        onBlur=&#123;()=&gt;&#123;</span></span><br><span class="line"><span class="xml">                            this.setState(&#123;</span></span><br><span class="line"><span class="xml">                                edit:false</span></span><br><span class="line"><span class="xml">                            &#125;)</span></span><br><span class="line"><span class="xml">                        &#125;&#125;</span></span><br><span class="line"><span class="xml">                    /&gt;</span> *<span class="regexp">/&#125;</span></span><br><span class="line"><span class="regexp">                    &lt;input </span></span><br><span class="line"><span class="regexp">                        ref=&#123;this.state.ref&#125;</span></span><br><span class="line"><span class="regexp">                        className="todo-input" </span></span><br><span class="line"><span class="regexp">                        type="text" </span></span><br><span class="line"><span class="regexp">                        value=&#123;val&#125;</span></span><br><span class="line"><span class="regexp">                        onChange=&#123;(e)=&gt;&#123;</span></span><br><span class="line"><span class="regexp">                           this.setState(&#123;</span></span><br><span class="line"><span class="regexp">                               val:e.target.value</span></span><br><span class="line"><span class="regexp">                           &#125;)</span></span><br><span class="line"><span class="regexp">                        &#125;&#125;</span></span><br><span class="line"><span class="regexp">                        onBlur=&#123;()=&gt;&#123;</span></span><br><span class="line"><span class="regexp">                            this.state.edit = false;</span></span><br><span class="line"><span class="regexp">                            if(val.trim())&#123;</span></span><br><span class="line"><span class="regexp">                                editTxt(id,val);</span></span><br><span class="line"><span class="regexp">                            &#125; else &#123;</span></span><br><span class="line"><span class="regexp">                                editTxt(id,txt);</span></span><br><span class="line"><span class="regexp">                            &#125;</span></span><br><span class="line"><span class="regexp">                        &#125;&#125;</span></span><br><span class="line"><span class="regexp">                    /</span>&gt;</span><br><span class="line">                &lt;<span class="regexp">/div&gt;</span></span><br><span class="line"><span class="regexp">            &lt;/</span>div&gt;</span><br><span class="line">        &lt;<span class="regexp">/li&gt;</span></span><br><span class="line"><span class="regexp">        )</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">export default Li;</span></span><br></pre></td></tr></table></figure>

<h2 id="state-js"><a href="#state-js" class="headerlink" title="state.js"></a>state.js</h2><p>主要是列表后面显示的待完成现象和已完成现象。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React,&#123;PureComponent&#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">State</span> <span class="keyword">extends</span> <span class="title">PureComponent</span> </span>&#123;</span><br><span class="line">  render()&#123;</span><br><span class="line">    <span class="keyword">let</span> &#123;data,removeDone&#125; = <span class="keyword">this</span>.props;</span><br><span class="line">    <span class="keyword">let</span> doneData = data.filter(<span class="function"><span class="params">item</span>=&gt;</span>item.done);</span><br><span class="line">    <span class="keyword">let</span> unDoneData = data.filter(<span class="function"><span class="params">item</span>=&gt;</span>!item.done);</span><br><span class="line">    <span class="keyword">return</span> (<span class="xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">"todo-stats"</span>&gt;</span></span></span><br><span class="line">            &lt;span className="todo-count"&gt;</span><br><span class="line">              &lt;span className="number"&gt;&#123;unDoneData.length&#125;&lt;/span&gt; &lt;span className="word"&gt;项待完成&lt;/span&gt;</span><br><span class="line"><span class="xml">            <span class="tag">&lt;/<span class="name">span</span>&gt;</span>  </span></span><br><span class="line">            &#123;doneData.length&lt;1?"":&lt;span className="todo-clear"&gt;</span><br><span class="line">              &lt;a </span><br><span class="line">                href="#"</span><br><span class="line">                onClick=&#123;()=&gt;&#123;</span><br><span class="line">                  removeDone();</span><br><span class="line">                &#125;&#125;</span><br><span class="line">              &gt;</span><br><span class="line">                  Clear &lt;span&gt;&#123;doneData.length&#125;&lt;/span&gt; 已完成事项</span><br><span class="line">              &lt;/a&gt;</span><br><span class="line">            &lt;/span&gt;&#125;</span><br><span class="line">            </span><br><span class="line"><span class="xml">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span>)</span></span><br><span class="line"><span class="xml">  &#125;</span></span><br><span class="line"><span class="xml">&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="xml">export default State;</span></span><br></pre></td></tr></table></figure>


      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/04/02/webpack/webpack%E5%8E%9F%E7%90%86/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/04/02/webpack/webpack%E5%8E%9F%E7%90%86/" class="post-title-link" itemprop="url">webpack原理分析</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-04-02 12:18:39" itemprop="dateCreated datePublished" datetime="2021-04-02T12:18:39+08:00">2021-04-02</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:22:52" itemprop="dateModified" datetime="2021-11-03T16:22:52+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/webpack/" itemprop="url" rel="index"><span itemprop="name">webpack</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>承接上一篇，本篇主要想说明的有三部分：1、webpack构建bundle的原理分析；2、自定义loader；3、自定义plugin。</p>
<h1 id="webpack打包原理"><a href="#webpack打包原理" class="headerlink" title="webpack打包原理"></a>webpack打包原理</h1><p>webpack打包时，到底发生了怎样的过程呢？我们实现了一个<strong>webpack_require</strong>来实现自己的模块化，把代码都缓存在installedModules里面，代码文件以对象传递进来，key为路径，value为包裹的代码字符串，且代码内部的require都被替换成了webpack_require。其步骤如下：chunk为出口代码块、bundle为入口资源文件。</p>
<p>1、接受一份配置：webpack.config.js。</p>
<p>2、分析出入口模块的位置：读取入口模块的内容、分析内容；哪些是依赖，哪些是源码；对于ES6、JSX其他需要编译的要进行编译，让浏览器能够执行；分析其他模块。</p>
<p>3、生成对象结构的数据：包括模块路径、和已经处理好的内容。</p>
<p>4、创建bundle.js：启动器函数，来补充代码中可能出现的module、exports、require，从而让浏览器能够顺利地执行。</p>
<h2 id="执行流程"><a href="#执行流程" class="headerlink" title="执行流程"></a>执行流程</h2><p>1、将命令行参数与 <code>webpack 配置文件</code> 合并、解析得到参数对象。</p>
<p>2、参数对象传给 webpack 执行得到 <code>Compiler</code> 对象。</p>
<p>3、执行 <code>Compiler</code> 的 <code>run</code>方法开始编译。每次执行 <code>run</code> 编译都会生成一个 <code>Compilation</code> 对象。</p>
<p>4、触发 <code>Compiler</code> 的 <code>make</code>方法分析入口文件，调用 <code>compilation</code> 的 <code>buildModule</code> 方法创建主模块对象。</p>
<p>5、生成入口文件 <code>AST(抽象语法树)</code>，通过 <code>AST</code> 分析和递归加载依赖模块。</p>
<p>6、所有模块分析完成后，执行 <code>compilation</code> 的 <code>seal</code> 方法对每个 <code>chunk</code> 进行整理、优化、封装。</p>
<p>7、最后执行 <code>Compiler</code> 的 <code>emitAssets</code> 方法把生成的文件输出到 <code>output</code> 的目录中。</p>
<h2 id="simpleWebpack"><a href="#simpleWebpack" class="headerlink" title="simpleWebpack"></a>simpleWebpack</h2><p>bundle.js是webpack启动的入口，也是整个构建的入口。主要获取配置文件，并以此为参数构建webpack实例，从而开启打包进程。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//执行webpack构建的入口</span></span><br><span class="line"><span class="comment">//1.拿到webpack.config.js配置</span></span><br><span class="line"><span class="keyword">const</span> options = <span class="built_in">require</span>(<span class="string">"./webpack.config.js"</span>);</span><br><span class="line"><span class="keyword">const</span> webpack = <span class="built_in">require</span>(<span class="string">"./lib/webpack.js"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> webpack(options).run();</span><br><span class="line"><span class="comment">//实例化一下webpack，并将配置传给它</span></span><br></pre></td></tr></table></figure>

<p>关键在于webpack.js，上面所说的三个步骤均在webpack.js中实现。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">"fs"</span>);</span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">"path"</span>);</span><br><span class="line"><span class="keyword">const</span> parser = <span class="built_in">require</span>(<span class="string">"@babel/parser"</span>);<span class="comment">//用babel来分析哪些是JS代码，哪些是模块化的引入</span></span><br><span class="line"><span class="keyword">const</span> traverse = <span class="built_in">require</span>(<span class="string">"@babel/traverse"</span>).default;<span class="comment">//用该模块来实现遍历AST。</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> &#123; transformFromAst &#125; = <span class="built_in">require</span>(<span class="string">"@babel/core"</span>);<span class="comment">//使用其transformFromAst的API</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="class"><span class="keyword">class</span> <span class="title">webpack</span> </span>&#123;</span><br><span class="line">  <span class="keyword">constructor</span>(options) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; entry, output &#125; = options;</span><br><span class="line">    <span class="keyword">this</span>.entry = entry;</span><br><span class="line">    <span class="keyword">this</span>.output = output;</span><br><span class="line">    <span class="keyword">this</span>.modules = [];</span><br><span class="line">  &#125; </span><br><span class="line">    <span class="comment">//定义一下启动函数run()</span></span><br><span class="line">  run() &#123;</span><br><span class="line">    <span class="comment">//开始分析入口模块的内容</span></span><br><span class="line">    <span class="keyword">const</span> info = <span class="keyword">this</span>.parse(<span class="keyword">this</span>.entry);</span><br><span class="line">    <span class="comment">//递归分析其他的模块 </span></span><br><span class="line">    <span class="comment">// console.log(info);</span></span><br><span class="line">    <span class="keyword">this</span>.modules.push(info);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="keyword">this</span>.modules.length; i++) &#123;</span><br><span class="line">      <span class="keyword">const</span> item = <span class="keyword">this</span>.modules[i];</span><br><span class="line">      <span class="keyword">const</span> &#123; dependencies &#125; = item;</span><br><span class="line">      <span class="keyword">if</span> (dependencies) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">let</span> j <span class="keyword">in</span> dependencies) &#123;</span><br><span class="line">          <span class="keyword">this</span>.modules.push(<span class="keyword">this</span>.parse(dependencies[j]));</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">const</span> obj = &#123;&#125;;</span><br><span class="line">    <span class="keyword">this</span>.modules.forEach(<span class="function">(<span class="params">item</span>) =&gt;</span> &#123;</span><br><span class="line">      obj[item.entryFile] = &#123;</span><br><span class="line">        dependencies: item.dependencies,</span><br><span class="line">        code: item.code,</span><br><span class="line">      &#125;;</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="comment">// console.log(obj);</span></span><br><span class="line">    <span class="keyword">this</span>.file(obj);</span><br><span class="line">  &#125;</span><br><span class="line">  parse(entryFile) &#123;</span><br><span class="line">    <span class="comment">//利用node中的fs模块来分析入口文件 </span></span><br><span class="line">    <span class="keyword">const</span> content = fs.readFileSync(entryFile, <span class="string">"utf-8"</span>);</span><br><span class="line">    <span class="comment">//首先要区分获取的内容中，哪些是模块依赖，哪些是JS代码</span></span><br><span class="line">    <span class="comment">//利用babel来实现这种划分; @babel/parser</span></span><br><span class="line">    <span class="comment">//const parser = require("@babel/parser");  </span></span><br><span class="line">    <span class="keyword">const</span> ast = parser.parse(content, &#123;</span><br><span class="line">      sourceType: <span class="string">"module"</span>,</span><br><span class="line">    &#125;);<span class="comment">//会返回一个AST抽象语法树</span></span><br><span class="line">    <span class="keyword">const</span> dependencies = &#123;&#125;;</span><br><span class="line">      <span class="comment">//利用traverse来遍历AST语法树，并对模块化的做提取处理,返回该类型的结点</span></span><br><span class="line">    traverse(ast, &#123;</span><br><span class="line">      ImportDeclaration(&#123; node &#125;) &#123;</span><br><span class="line">        <span class="comment">//   "./a.js" =&gt; "./src/a.js" 拼接路径结构</span></span><br><span class="line">        <span class="keyword">const</span> newPathName =</span><br><span class="line">          <span class="string">"./"</span> + path.join(path.dirname(entryFile), node.source.value);</span><br><span class="line">        <span class="comment">// console.log(newPathName);处理好路径之后，存储在dependencies数组中</span></span><br><span class="line">        dependencies[node.source.value] = newPathName;</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">const</span> &#123; code &#125; = transformFromAst(ast, <span class="literal">null</span>, &#123;</span><br><span class="line">      presets: [<span class="string">"@babel/preset-env"</span>],<span class="comment">//利用babel来对内容进行处理</span></span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      entryFile,</span><br><span class="line">      dependencies,</span><br><span class="line">      code,</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line">  file(code) &#123;</span><br><span class="line">    <span class="comment">//创建自运行函数，处理require,module,exports</span></span><br><span class="line">    <span class="comment">//生成main.js = &gt;dist/main.js</span></span><br><span class="line">    <span class="keyword">const</span> filePath = path.join(<span class="keyword">this</span>.output.path, <span class="keyword">this</span>.output.filename);</span><br><span class="line">    <span class="built_in">console</span>.log(filePath);</span><br><span class="line">    <span class="comment">//require("./a.js")</span></span><br><span class="line">    <span class="comment">// this.entry = "./src/index.js"</span></span><br><span class="line">    <span class="keyword">const</span> newCode = <span class="built_in">JSON</span>.stringify(code);</span><br><span class="line">    <span class="keyword">const</span> bundle = <span class="string">`(function(graph)&#123;</span></span><br><span class="line"><span class="string">        function require(module)&#123;</span></span><br><span class="line"><span class="string">            function reRequire(relativePath)&#123;</span></span><br><span class="line"><span class="string">                return require(graph[module].dependencies[relativePath]) </span></span><br><span class="line"><span class="string">            &#125;</span></span><br><span class="line"><span class="string">            var exports = &#123;&#125;;</span></span><br><span class="line"><span class="string">            (function(require,exports,code)&#123;</span></span><br><span class="line"><span class="string">                eval(code)</span></span><br><span class="line"><span class="string">            &#125;)(reRequire,exports,graph[module].code)</span></span><br><span class="line"><span class="string">            return exports;</span></span><br><span class="line"><span class="string">        &#125;</span></span><br><span class="line"><span class="string">        require('<span class="subst">$&#123;<span class="keyword">this</span>.entry&#125;</span>')</span></span><br><span class="line"><span class="string">    &#125;)(<span class="subst">$&#123;newCode&#125;</span>)`</span>;</span><br><span class="line">    fs.writeFileSync(filePath, bundle, <span class="string">"utf-8"</span>);<span class="comment">//把bundle中的内容写到main.js里面。</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h1 id="编写loader"><a href="#编写loader" class="headerlink" title="编写loader"></a>编写loader</h1><p>Loader的本质就是一个函数，声明式函数而不能用箭头函数；拿到源代码，再返回处理后的代码即可。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span> (<span class="params">source</span>) </span>&#123;</span><br><span class="line">  <span class="comment">//loader处理模块</span></span><br><span class="line">  <span class="comment">//多个loader是有顺序的 </span></span><br><span class="line">  <span class="comment">//一定要有返回值</span></span><br><span class="line">  <span class="comment">//   console.log(this, this.query, source);通过this、this.query来获取给loader传递的参数</span></span><br><span class="line">  <span class="comment">//   console.log(result);</span></span><br><span class="line">  <span class="keyword">const</span> callback = <span class="keyword">this</span>.async();</span><br><span class="line">  setTimeout(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> result = source.replace(<span class="string">"hello"</span>, <span class="keyword">this</span>.query.name);</span><br><span class="line">    <span class="comment">// return result;</span></span><br><span class="line">    callback(<span class="literal">null</span>, result);</span><br><span class="line">  &#125;, <span class="number">3000</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">//   return result;</span></span><br><span class="line">  <span class="comment">//this.callback(),返回多个信息</span></span><br><span class="line">  <span class="comment">//   this.callback(null, result);</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>在配置文件中使用loader时，要如何让loader接受参数：this.query、loader-utils。</p>
<p>this.callback：用于返回多个信息；this.async：如果loader里面有异步的消息需要去处理时，</p>
<p>当有多个loader时，会有执行顺序，自后往前、自下往上，</p>
<p>处理loader的路径问题，在配置文件中使用resolveLoader。</p>
<h1 id="编写Plugin"><a href="#编写Plugin" class="headerlink" title="编写Plugin"></a>编写Plugin</h1><p>Plugin：开始打包，在某个示刻，帮助我们处理一些事情的机制。Plugin要比loader稍微复杂一些，在webpack的源码中plugin的机制占据了很多的场景，可以说是webpack的灵魂。</p>
<p>Plugin的本质是一个类，里面包含一个apply函数，接受一个参数compiler。</p>
<p>设计模式包括：事件驱动、发布订阅。</p>
<p>webpack 就像一条生产线，要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的，多个流程之间有存在依赖关系，只有完成当前处理后才能交给下一个流程去处理。</p>
<p>插件就像是一个插入到生产线中的一个功能，在特定的时机对生产线上的资源做处理。webpack 通过 Tapable 来组织这条复杂的生产线。 webpack 在运行过程中会广播事件，插件只需要监听它所关心的事件，就能加入到这条生产线中，去改变生产线的运作。webpack 的事件流机制保证了插件的有序性，使得整个系统扩展性很好。</p>
<p>站在代码逻辑的角度就是：webpack 在编译代码过程中，会触发一系列 Tapable 钩子事件，插件所做的，就是找到相应的钩子，往上面挂上自己的任务，也就是注册事件，这样，当 webpack 构建的时候，插件注册的事件就会随着钩子的触发而执行了。其实跟vue的生命周期的执行方式本质上相差无几。</p>
<h2 id="plugin-的基本结构"><a href="#plugin-的基本结构" class="headerlink" title="plugin 的基本结构"></a>plugin 的基本结构</h2><p>一个 webpack plugin 由如下部分组成：</p>
<ol>
<li>一个命名的 Javascript 方法或者 JavaScript 类。</li>
<li>它的原型上需要定义一个叫做 <code>apply</code> 的方法。</li>
<li>注册一个事件钩子。</li>
<li>操作webpack内部实例特定数据。</li>
<li>功能完成后，调用webpack提供的回调。</li>
</ol>
<p>一个基本的 plugin 代码结构大致长这个样子：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// plugins/MyPlugin.js</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyPlugin</span> </span>&#123;</span><br><span class="line">  apply(compiler) &#123;</span><br><span class="line">    compiler.hooks.done.tap(<span class="string">'My Plugin'</span>, (stats) =&gt; &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">'Bravo!'</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = MyPlugin;</span><br></pre></td></tr></table></figure>

<p>这就是一个最简单的 webpack 插件了，它注册了 <code>Compiler</code> 上的异步串行钩子 <code>done</code>，在钩子中注入了一条控制台打印的语句。根据上文钩子的介绍我们可以知道，<code>done</code> 会在一次编译完成后执行。所以这个插件会在每次打包结束，向控制台首先输出这句 <code>Bravo!</code>。</p>
<p><img src="/webpack%E5%8E%9F%E7%90%86.assets/16fa85e8e9265825" alt="webpack-plugin1.jpg"></p>
<h2 id="开发一个文件清单插件"><a href="#开发一个文件清单插件" class="headerlink" title="开发一个文件清单插件"></a>开发一个文件清单插件</h2><p>我希望每次webpack打包后，自动产生一个打包文件清单，上面要记录文件名、文件数量等信息。</p>
<h3 id="思路："><a href="#思路：" class="headerlink" title="思路："></a>思路：</h3><ul>
<li>显然这个操作需要在文件生成到dist目录之前进行，所以我们要注册的是<code>Compiler</code>上的<code>emit</code>钩子。</li>
<li><code>emit</code> 是一个异步串行钩子，我们用 <code>tapAsync</code> 来注册。</li>
<li>在 <code>emit</code> 的回调函数里我们可以拿到 <code>compilation</code> 对象，所有待生成的文件都在它的 <code>assets</code> 属性上。</li>
<li>通过 <code>compilation.assets</code> 获取我们需要的文件信息，并将其整理为新的文件内容准备输出。</li>
<li>然后往 <code>compilation.assets</code> 添加这个新的文件。</li>
</ul>
<p>插件完成后，最后将写好的插件放到 webpack 配置中，这个包含文件清单的文件就会在每次打包的时候自动生成了。</p>
<h3 id="实现："><a href="#实现：" class="headerlink" title="实现："></a>实现：</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// plugins/FileListPlugin.js</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">FileListPlugin</span> </span>&#123;</span><br><span class="line">    <span class="keyword">constructor</span> (options) &#123;</span><br><span class="line">    	<span class="comment">// 获取插件配置项</span></span><br><span class="line">        <span class="keyword">this</span>.filename = options &amp;&amp; options.filename ? options.filename : <span class="string">'FILELIST.md'</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    apply(compiler) &#123;</span><br><span class="line">        <span class="comment">// 注册 compiler 上的 emit 钩子</span></span><br><span class="line">        compiler.hooks.emit.tapAsync(<span class="string">'FileListPlugin'</span>, (compilation, cb) =&gt; &#123;</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 通过 compilation.assets 获取文件数量</span></span><br><span class="line">            <span class="keyword">let</span> len = <span class="built_in">Object</span>.keys(compilation.assets).length;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 添加统计信息</span></span><br><span class="line">            <span class="keyword">let</span> content = <span class="string">`# <span class="subst">$&#123;len&#125;</span> file<span class="subst">$&#123;len&gt;<span class="number">1</span>?<span class="string">'s'</span>:<span class="string">''</span>&#125;</span> emitted by webpack\n\n`</span>;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 通过 compilation.assets 获取文件名列表</span></span><br><span class="line">            <span class="keyword">for</span>(<span class="keyword">let</span> filename <span class="keyword">in</span> compilation.assets) &#123;</span><br><span class="line">                content += <span class="string">`- <span class="subst">$&#123;filename&#125;</span>\n`</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 往 compilation.assets 中添加清单文件</span></span><br><span class="line">            compilation.assets[<span class="keyword">this</span>.filename] = &#123;</span><br><span class="line">            	<span class="comment">// 写入新文件的内容</span></span><br><span class="line">                source: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">                    <span class="keyword">return</span> content;</span><br><span class="line">                &#125;,</span><br><span class="line">                <span class="comment">// 新文件大小（给 webapck 输出展示用）</span></span><br><span class="line">                size: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">                    <span class="keyword">return</span> content.length;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 执行回调，让 webpack 继续执行</span></span><br><span class="line">            cb();</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = FileListPlugin;</span><br></pre></td></tr></table></figure>
      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  


  
  <nav class="pagination">
    <a class="extend prev" rel="prev" href="/page/5/"><i class="fa fa-angle-left" aria-label="上一页"></i></a><a class="page-number" href="/">1</a><span class="space">&hellip;</span><a class="page-number" href="/page/5/">5</a><span class="page-number current">6</span><a class="page-number" href="/page/7/">7</a><span class="space">&hellip;</span><a class="page-number" href="/page/10/">10</a><a class="extend next" rel="next" href="/page/7/"><i class="fa fa-angle-right" aria-label="下一页"></i></a>
  </nav>



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="Li Yudong"
      src="/images/author.jpg">
  <p class="site-author-name" itemprop="name">Li Yudong</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">95</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">14</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">18</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/tiarmor1" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;tiarmor1" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:1157019137@qq.com" title="E-Mail → mailto:1157019137@qq.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>
  <div class="cc-license motion-element" itemprop="license">
    <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" class="cc-opacity" rel="noopener" target="_blank"><img src="/images/cc-by-nc-sa.svg" alt="Creative Commons"></a>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 2020 – 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Li Yudong</span>
</div>

        








      </div>
    </footer>
  </div>

  
  
  <script color='0,0,0' opacity='0.5' zIndex='-1' count='150' src="/lib1/canvas-nest/canvas-nest.min.js"></script>
  <script src="/lib1/anime.min.js"></script>
  <script src="/lib1/velocity/velocity.min.js"></script>
  <script src="/lib1/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  




  
<script src="/js/local-search.js"></script>













  

  

  
   <canvas class="fireworks" style="position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;" ></canvas> 
   <script type="text/javascript" src="//cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script> 
   <script type="text/javascript" src="/js/fireworks.js"></script>
  
</body>
</html>

